LIBRERIAS

Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages ------------------------------------------------------------------------ tidyverse 1.3.1 --
v tibble  3.1.1     v dplyr   1.0.5
v tidyr   1.1.3     v stringr 1.4.0
v readr   1.4.0     v forcats 0.5.1
v purrr   0.3.4     
-- Conflicts --------------------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
Loading required package: gsubfn
Loading required package: proto
Loading required package: RSQLite

Attaching package: 㤼㸱lubridate㤼㸲

The following objects are masked from 㤼㸱package:base㤼㸲:

    date, intersect, setdiff, union

Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Loading required package: Matrix

Attaching package: 㤼㸱Matrix㤼㸲

The following objects are masked from 㤼㸱package:tidyr㤼㸲:

    expand, pack, unpack


Attaching package: 㤼㸱arules㤼㸲

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    recode

The following objects are masked from 㤼㸱package:base㤼㸲:

    abbreviate, write

CARGA DE DATOS

df_artist <- read.csv("data/df_artist_sin_duplicados.csv")
df_charts_raw <- read.csv("data/df_charts_sin_duplicados.csv")
df_audio_features_raw <- read.csv("data/audio_features_plano_sin_duplicados.csv")
df_lyrics <- read.csv("data/df_lyrics.csv")

Corrección duplicados

# DF listo para el join con chrats
df_audio_features <- df_audio_features_raw %>% 
  group_by(track_name, external_urls_spotify) %>% 
  mutate(artist_all = paste(artist_name, collapse = ",|,")) %>%
  ungroup() %>% 
  mutate(artist_key = sub(",|,.*", "", artist_all)) %>% 
  dplyr::select(artist_name, artist_all, artist_key, everything(.)) %>% 
  distinct(artist_key, external_urls_spotify, .keep_all = T) %>% 
  as.data.frame()

Creacion cant_markets

contar_market <- function(x){
q <- length(unlist(strsplit(x, split = ",")))
return (q)
  }
df_audio_features$cant_markets <- sapply(df_audio_features[,"markets_concat"], contar_market)

Vectores de features

CHARTS: agregación de features

#metrica de popularidad
df_charts <- df_charts_raw %>% 
  group_by(Artist, Track_Name, URL) %>%
  dplyr:: summarise(semanas_sum = n(),
            streams_sum = (sum(Streams, na.rm = T)/10^6 ),
            streams_min = (min(Streams)/10^6 ),
            streams_max = (max(Streams)/10^6 ),
            position_avg = mean(Position, na.rm = T),
            position_min = min(Position), 
            position_max = max(Position)) %>% 
  ungroup() %>% 
  mutate(popularidad = as.numeric(streams_sum*semanas_sum/position_avg) )
`summarise()` has grouped output by 'Artist', 'Track_Name'. You can override using the `.groups` argument.

Agregación de todas las semanas en charts


groupping_cols <- c("artist_name","artist_all","artist_key",
                    "track_name","external_urls_spotify","album_name","album_release_year")

numeric_col_charts <- c("Position","Streams")

week_start <- c("week_start")

chart_group <- join_audio_charts %>% 
                group_by(artist_name,artist_all,artist_key,track_name,
                         external_urls_spotify,album_name,album_release_year)


continuas_summarized = chart_group %>% summarise_at(features_continuas, mean, na.rm = TRUE)
categoricas_summarizes = chart_group %>% summarise_at(features_categoricas, first)
numeric_charts_summarizes = chart_group %>% summarise(across(numeric_col_charts,
                                                             list(min=min,max=max,avg=mean)))

cant_semanas = chart_group %>% summarise_at(week_start, n_distinct)
names(cant_semanas$week_start) <- "cant_semanas"

aggregation_df <- cbind(numeric_charts_summarizes,
                        cant_semanas[,-c(1:7)],continuas_summarized[,-c(1:7)], 
                        categoricas_summarizes[,-c(1:7)])

names(aggregation_df)[names(aggregation_df) == 'week_start'] <- "cant_semanas"

cols <- names(aggregation_df)
numeric_cols <- cols[sapply(aggregation_df,is.numeric)]

summary(aggregation_df[,numeric_cols[2:length(numeric_cols)]])

RIGTH JOIN audio_features Y charts

md.pattern(join_audio_charts, rotate.names = TRUE)
     artist_key track_name external_urls_spotify semanas_sum streams_sum streams_min
1975          1          1                     1           1           1           1
2             1          1                     1           1           1           1
1326          1          1                     1           1           1           1
              0          0                     0           0           0           0
     streams_max position_avg position_min position_max popularidad artist_name
1975           1            1            1            1           1           1
2              1            1            1            1           1           1
1326           1            1            1            1           1           0
               0            0            0            0           0        1326
     artist_all album_name acousticness danceability duration_ms energy instrumentalness
1975          1          1            1            1           1      1                1
2             1          1            1            1           1      1                1
1326          0          0            0            0           0      0                0
           1326       1326         1326         1326        1326   1326             1326
     liveness loudness speechiness tempo valence cant_markets explicit key_name
1975        1        1           1     1       1            1        1        1
2           1        1           1     1       1            1        1        1
1326        0        0           0     0       0            0        0        0
         1326     1326        1326  1326    1326         1326     1326     1326
     mode_name key_mode album_release_year      
1975         1        1                  1     0
2            1        1                  0     1
1326         0        0                  0    19
          1326     1326               1328 25196

HISTOGRAMAS Y BARPLOTS DE VARIABLES


##histograma de las variables continuas de audio_features

for (i in features_continuas){

  hist(df_audio_features[,i], main = paste("Histograma de", i, "(all data)"), xlab = i)
  abline(v = mean(df_audio_features[,i], na.rm = TRUE) , col="red")
  abline(v = median(df_audio_features[,i], na.rm = TRUE) , col="blue")
  legend("topright", legend = c("Media", "Mediana"), col=c("red", "blue"), lty =1)

}

#divido los features por su distribución
features_continuas_media <- c('danceability', 'tempo', 'valence')

features_continuas_mediana <- c('acousticness', 'duration_ms', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets')


##histograma de las variables continuas de charts
for (i in c(features_continuas)){

  hist(join_audio_charts[,i], main = paste("Histograma de", i,  "(charts)"), xlab = i)
  abline(v = mean(join_audio_charts[,i], na.rm = TRUE) , col="red")
  abline(v = median(join_audio_charts[,i], na.rm = TRUE) , col="blue")

}

#divido features de charts según su distribución
audio_charts_continuas_media <- c('duration_ms', 'valence')

audio_charts_continuas_mediana <- c('danceability', 'acousticness', 'tempo', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets', "Streams")


##medidas resumen y barplots de las variables categoricas audio_features
for(i in features_categoricas){

  barplot(sort(table(df_audio_features[,i]),decreasing = T), las=2, 
          main = paste("Barplot de", i, "(all data)"))
  # pie(table(df_features_categoricos[,i]))
}



##medidas resumen y barplots de las variables categoricas join_audio_charts

for(i in features_categoricas){
  barplot(table(join_audio_charts[,i]), las=2,
          main = paste("Barplot de", i, "(charts)")
          )
  # pie(table(df_features_categoricos[,i]))
}

SESGO DE VARIABLES

Boxplots Variables Numéricas sin filtrar outliers

par(mfrow=c(4,3))
for (feature in features_continuas){
  boxplot(df_audio_features[,feature], las=2, horizontal=T, main=feature)
}

Con excepción de valence el resto de las features poseían cierto sesgo. Se decidió transformar las variables que mayor sesgo poseían: duration_ms, instrumentalness, liveness, speechiness como método de corregir la distribución y achicar la cantidad de outliers. La variable loudness_reg_imp no fue modificada debido a que al ser negativa

Transformaciones

Transformación logarítmica

#join entre variables transformadas y resto features
x <- df_audio_features %>% 
  select("artist_name","artist_all","artist_key",
         "track_name", "external_urls_spotify", "album_name", "album_release_year",
         all_of(features_continuas), all_of(features_categoricas)) %>%
  select(!variables_sesgo) 

join_audio_charts <- cbind(x, df_sesgadas_log_adjust) %>% 
  right_join( df_charts %>%
               select( "Track_Name", "Artist", 
                       "URL","Position", "Streams", "week_start", "week_end"),
               by = c("track_name" = "Track_Name", 
                      "artist_key" ="Artist", 
                      "external_urls_spotify" = "URL"))

variables_plot <- unlist(strsplit("duration_ms", ","))
variables_plot <- append(variables_plot, paste(variables_plot,"_log", sep=""))
variables_plot <- variables_plot[order(variables_plot)]
plotear <- merged[,variables_plot]

par(mfrow = c(1,2))
for (col in names(plotear)){
  hist(plotear[,col], breaks="FD", main=col, xlab="")
}

transformacion <- c('instrumentalness','loudness','liveness','speechiness', 'duration_ms')

logaritmo_ajustado = function(x,delta){
  if (x<=0.0){
    return(log(0.00+delta, base = 10))
  }else{
    return(log(x, base = 10))
  }
}

delta <- 10^(-6)

par(mfrow=c(2,5))
for (feature in transformacion){
  hist(df_audio_features[,feature], main=feature)
}

for (feature in transformacion){
  hist(unlist(lapply(df_audio_features[,feature], function(x) logaritmo_ajustado(x,delta))), main=paste(feature,"log", sep="_"))
}

inv_sqrt_ajustada = function(x, delta){
  if (x==0.0){
    return(1/sqrt(x+delta))
  }else{
    return(1/sqrt(x))
  }
}


delta <- 10^(-6)

par(mfrow=c(2,5))
for (feature in transformacion){
  hist(df_audio_features[,feature], main=feature)
}
for (feature in transformacion){
  hist(unlist(lapply(df_audio_features[,feature], function(x) inv_sqrt_ajustada(x,delta))), main=paste(feature,"inv_sqt", sep="_"))
}


par(mfrow=c(2,5))
for (feature in transformacion){
  hist(df_audio_features[,feature], main=feature)
}
for (feature in transformacion){
  hist(sqrt(df_audio_features[,feature]), main=paste(feature,"sqrt", sep="_"))
}

par(mfrow = c(2,1)) 
hist(df_audio_features[,'loudness_reg_imp'], main='loudness', xlab="")
#hist(sqrt(df_audio_features[,'loudness_reg_imp']), main= 'loudness_sqrt', xlab="")
boxplot(df_audio_features[,'loudness_reg_imp'], horizontal = T)
#boxplot(sqrt(df_audio_features[,'loudness_reg_imp']), horizontal = T)
fit <- lm(loudness~energy+acousticness, data=df_audio_features)

modelo <- fit$coefficients

df_audio_features$loudness_reg_imp <- df_audio_features$loudness

X <- df_audio_features[df_audio_features$loudness>0, c('energy', "acousticness")]

df_audio_features$loudness_reg_imp[df_audio_features$loudness>0] <- modelo[1]+modelo[2]*X[,1]+modelo[3]*X[,2]

summary(df_audio_features[,c("loudness", "loudness_reg_imp")])

summary(fit)

instrumentalness tiene mucho sesgo la variable. Se va a recurrir a una logaritmización de la variable, previa transformación del dominio, haciendo que los valores que son 0, sean en realidad 0.0000001

logaritmo_ajustado = function(x,delta){
  if (x==0.0){
    return(log(x+delta, base = 10))
  }else{
    return(log(x, base = 10))
  }
}

delta <- 10^(-6)

df_audio_features$instrumentalness_logadjust <- unlist(lapply(df_audio_features$instrumentalness, function(x) logaritmo_ajustado(x,delta)))

par(mfrow =c(2,2))
hist(df_audio_features$instrumentalness, main="insrumentalness", xlab="")
hist(unlist(lapply(df_audio_features$instrumentalness, function(x) logaritmo_ajustado(x,delta))), main='instrumentalness_logadjust', ylim = c(0,130500), xlab = "")
boxplot(df_audio_features$instrumentalness, main="", horizontal = T)
boxplot(unlist(lapply(df_audio_features$instrumentalness, function(x) logaritmo_ajustado(x,delta))), main="", horizontal=T)
# hist(log(1/sqrt(df_audio_features$instrumentalness+0.00001)),main='log(sqrt(x+))', ylim=c(0,130500), xlab = "")

¿Es útil esta transformación?


delta <- 10^(-6)

df_audio_features$instrumentalness_logadjust <- unlist(lapply(df_audio_features$instrumentalness, function(x) logaritmo_ajustado(x,delta)))

df_chart_tojoin <- df_charts[,c("Track_Name", "Artist", "URL")]
df_chart_tojoin$isinchart <- 1
df_audio_features_tojoin <- df_audio_features[, c("track_name","artist_key","external_urls_spotify","instrumentalness", "instrumentalness_logadjust")]

join_histogram <- df_audio_features_tojoin %>% 
  dplyr::select("track_name","artist_key","external_urls_spotify","instrumentalness", "instrumentalness_logadjust") %>% 
  left_join( df_chart_tojoin %>%
               select("Track_Name", "Artist", "URL","isinchart"),
               by = c(
                 "track_name" = "Track_Name", 
                      "artist_key" ="Artist", 
                      "external_urls_spotify" = "URL"))


join_histogram$isinchart[is.na(join_histogram$isinchart)] <- 0

join_histogram$isinchart <- factor(join_histogram$isinchart)


h11 <- hist(join_histogram[join_histogram$isinchart==1,'instrumentalness'])
h11$density <-  h11$counts/sum(h11$counts)*100

h12 <- hist(join_histogram[join_histogram$isinchart==0,'instrumentalness'])
h12$density <-  h12$counts/sum(h12$counts)*100

h21 <- hist(join_histogram[join_histogram$isinchart==1,'instrumentalness_logadjust'])
h21$density <-  h21$counts/sum(h21$counts)*100

h22 <- hist(join_histogram[join_histogram$isinchart==0,'instrumentalness_logadjust'])
h22$density <-  h22$counts/sum(h22$counts)*100

#png("C:/Users/Asus/Desktop/DATA SCIENCE/MAESTRIA/Data Mining/TP/graficos/instrumentalness.png",
#    width = 800, height = 800)
par(mfrow = c(3,2))
plot(h11, main='instrumentalness \nchart', xlab="", ylab="Porcentage", freq=FALSE, col='grey', ylim = c(0,100))
plot(h12, main='instrumentalness \nfuera chart', xlab="", ylab="Porcentage", freq=FALSE, col='grey', ylim = c(0,100))
plot(h21, main ="instrumentalness_log \nchart", xlab="", ylab="Porcentage", freq=FALSE, col='grey', ylim = c(0,100))
plot(h22, main ="instrumentalness_log \nfuera chart", xlab="", ylab="Porcentage", freq=FALSE, col='grey', ylim = c(0,100))
boxplot(join_histogram[join_histogram$isinchart==1,'instrumentalness_logadjust'], main="instrumentalness_log chart", horizontal = T)
boxplot(join_histogram[join_histogram$isinchart==0,'instrumentalness_logadjust'], main="instrumentalness_log fuera chart", horizontal = T)
#dev.off()

Z-Score de Variables que “tienden a la normal”


################################

## FILTRAMOS OUTLIERS POR Z-SCORE para 'danceability', 'tempo', 'valence'

##############################

#z-score para variables que tienden a la normal
#filtro features numericos 

#divido los features por su distribución
features_continuas_media <- c('danceability', 'tempo', 'valence')
df_audio_features_zscore_media <- df_audio_features[,features_continuas_media]

#normalizo z score con las variables que tienden a la normal

zscore_cols <- c()
for(col in names(df_audio_features_zscore_media)){
  name_col <- paste('zscore_',col, sep = "")
  zscore_cols <- append(zscore_cols, name_col)
  media <-  mean(df_audio_features_zscore_media[,col])
  stdv <- sd(df_audio_features_zscore_media[,col])
  df_audio_features_zscore_media[,name_col] <- (df_audio_features_zscore_media[,col] - media)/stdv
  }

par(mfrow=c(1,length(zscore_cols)))
lapply(zscore_cols, function(col) boxplot(df_audio_features_zscore_media[,col],xlab=col))

Analisis de Z-Score por variable

Danceability

#variable: danceability

umbral_zscore <- 3
conditions <- (df_audio_features_zscore_media$zscore_danceability> umbral_zscore) | (df_audio_features_zscore_media$zscore_danceability< -1*umbral_zscore)
df_audio_features[conditions,] %>%
  select(album_name,artist_name, danceability ) %>%
  arrange(-danceability)

Tempo

#variable: Tempo

umbral_zscore <- 3
conditions <- (df_audio_features_zscore_media$zscore_tempo> umbral_zscore) | (df_audio_features_zscore_media$zscore_tempo< -1*umbral_zscore)
df_audio_features[conditions,] %>%
  select(album_name,artist_name, tempo ) %>%
  arrange(-tempo)

Valence

#variable: valence
umbral_zscore <- 3
conditions <- (df_audio_features_zscore_media$zscore_valence> umbral_zscore) | (df_audio_features_zscore_media$zscore_valence< -1*umbral_zscore)
df_audio_features[conditions,] %>%
  select(album_name,artist_name, valence ) %>%
  arrange(-valence)

Z-Score Modificado de Variables Asimetricas

################################

## FILTRAMOS OUTLIERS POR Z-SCORE MODIFICADO para 'acousticness', 'duration_ms', 'energy',  'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets'

##############################

features_continuas_mediana <- c('acousticness', 'duration_ms', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets')

df_audio_features_zscore_mediana <- df_audio_features[,features_continuas_mediana]



zscoremodif_cols <- c()
for(col in names(df_audio_features_zscore_mediana)){
  name_col <- paste('zscoremodif_',col, sep = "")
  zscoremodif_cols <- append(zscoremodif_cols, name_col)
  med = median(df_audio_features_zscore_mediana[,col], na.rm = T)
  MAD = median(abs(df_audio_features_zscore_mediana[,col] - med), na.rm = T)
  df_audio_features_zscore_mediana[, name_col] <- 0.6745 * (df_audio_features_zscore_mediana[,col] - med) / MAD
}


par(mfrow=c(4,2))
lapply(zscoremodif_cols, function(col) boxplot(df_audio_features_zscore_mediana[,col],xlab=col, horizontal = T))

Revisión Variable Instrumentalness

instrumentalness <- c("instrumentalness", "zscoremodif_instrumentalness") 

x <- df_audio_features$instrumentalness

n_interv <- 10


intervalos <- round(seq(0,max(x),by=(max(x)-min(x))/n_interv),2)

labs <- c()
for (i in 1:n_interv){
lab <- paste(intervalos[i],intervalos[i+1], sep='\n')
labs <- append(labs, lab)
    
}

bins <- cut(x, n_interv, include.lowest = TRUE, labels = labs)

barplot(table(bins))

Hacemos K-means para poder discretizar la variable.

sse <- c()
for (k in 2:6){
  clusters <- kmeans(df_audio_features$instrumentalness,centers = k, iter.max = 10, nstart = k)
  sse <- append(sse, clusters$tot.withinss)
}

plot(2:6,sse, type = 'l', xlab='Cantidad de Clusters', ylab='Suma Error Cuadrático')

#k=3 
clusters3 <- kmeans(df_audio_features$instrumentalness,centers = 3, iter.max = 10, nstart = 3)

df_audio_features$clusters <- factor(clusters3$cluster)

lev <- levels(df_audio_features$clusters)

labs <- c()
for (i in lev){
  min <- min(df_audio_features$instrumentalness[df_audio_features$clusters==i])
  max <- max(df_audio_features$instrumentalness[df_audio_features$clusters==i])
  lab <- paste(min,max, sep=' - ')
  labs <- append(labs, lab)
}

labs

# barplot(table(factor(clusters3$cluster)), labels = labs)

#prueba igal de transformacion y test de normalidad

join_audio_charts[1:5,"acousticness"]^2

library(nortest)

log10(df_chart_w_lyrics$acousticness)

for (i in features_continuas){
   x <- log10(df_chart_w_lyrics[,i])
   x <- shapiro.test(x)
   z <- x$p.value
  print(z)
  }

LYRICS

Filtro por idioma

# lyrics = mongo(collection = "lyrics", db = "spotify_dm" )
# df_lyrics <- lyrics$find('{}')
# 
# write.csv(df_lyrics, "data/df_lyrics.csv")

df_lyrics <- read.csv("data/df_lyrics.csv") %>% 
  select(-X)

df_lyrics_unicas <- df_lyrics %>% 
  distinct(artist_name, track_name, lyrics)


#filtro de idioma
spa_lyrics = df_lyrics_unicas[textcat(df_lyrics_unicas$lyrics)=="spanish",]
spa_lyrics

en_lyrics = df_lyrics_unicas[textcat(df_lyrics_unicas$lyrics) %in% c("english", "scots"),]
en_lyrics

#chequeo cantidad de canciones por idioma
100*(nrow(en_lyrics) + nrow(spa_lyrics))/nrow(df_lyrics_unicas)

# tabla contingencia de idiomas
idiomas = textcat(df_lyrics_unicas$lyrics)
# sort(table(idiomas), decreasing = T)

limpieza español

# comentar y descomentar según se elija un dataframe u otro
# df_lyrics_seleccionado = df_lyrics_unicas
df_lyrics_seleccionado = en_lyrics

corpus = Corpus(VectorSource(enc2utf8(df_lyrics_seleccionado$lyrics)))

# Eliminamos espacios
corpus.pro <- tm_map(corpus, stripWhitespace)
inspect(corpus.pro[1])

# Elimino todo lo que aparece antes del primer []
corpus.pro <- tm_map(corpus.pro, content_transformer(
  function(x) sub('^.+?\\[.*?\\]',"", x)))
# inspect(corpus.pro[1])

# Elimino las aclaraciones en las canciones, por ejemplo:
# [Verso 1: Luis Fonsi & Daddy Yankee]
corpus.pro <- tm_map(corpus.pro, content_transformer(
  function(x) gsub('\\[.*?\\]', '', x)))

# Elimino todo lo que aparece luego de 'More on Genius'
corpus.pro <- tm_map(corpus.pro, content_transformer(function(x) gsub("More on Genius.*","", x)))

# Convertimos el texto a minúsculas
corpus.pro <- tm_map(corpus.pro, content_transformer(tolower))

# removemos números
corpus.pro <- tm_map(corpus.pro, removeNumbers)

# Podemos agregar palabras a las stopwords
# my_stopwords <- append(stopwords("spanish"), 'palabra')
my_stopwords <- append(stopwords("english"), c('yeah', "aint", "get", "got"))

# Removemos palabras vacias 
corpus.pro <- tm_map(corpus.pro, removeWords, stopwords("english"))
corpus.pro <- tm_map(corpus.pro, removeWords, my_stopwords)
# corpus.pro <- tm_map(corpus.pro, removeWords, stopwords("spanish"))
# inspect(corpus.pro[1])


# Removemos puntuaciones
corpus.pro <- tm_map(corpus.pro, removePunctuation)

# Removemos todo lo que no es alfanumérico
corpus.pro <- tm_map(corpus.pro, content_transformer(function(x) str_replace_all(x, "[[:punct:]]", " ")))

# En tm_map podemos utilizar funciones prop
library(stringi)
replaceAcentos <- function(x) {stri_trans_general(x, "Latin-ASCII")}
corpus.pro <- tm_map(corpus.pro, replaceAcentos)

# Eliminamos espacios que se van generando con los reemplazos
corpus.pro <- tm_map(corpus.pro, stripWhitespace)

limpieza ingles

#funciones
#funcion para corregir palabras
decontracted = function(txt){
  txt = gsub("won't", "will not", txt)
  txt = gsub("\\'s", " is", txt)
  txt = gsub("\\'t", " not", txt)
  txt = gsub("\\'ll", " will", txt)
  txt = gsub("\\'m", " am", txt)
  txt = gsub("\\'re", " are", txt)
  txt = gsub("\\'d", " had", txt)
  txt = gsub("\\'ve", " have", txt)
  txt = gsub("couldn", "could", txt)
  txt = gsub("don", "do", txt)
  txt = gsub("doesn", "does", txt)
  txt = gsub("isn", "is", txt)
  txt = gsub("mustn", "must", txt)
  txt = gsub("shouldn", "should", txt)
  txt = gsub("wasn", "was", txt)
  txt = gsub("\\'cause", " because", txt)
  txt = gsub("\\'", "g", txt)
  return(txt)
}


#Función para limpiar. 
text_cleaning = function(txt, stop=FALSE, language){
  
  txt = sub('^.+?\\[.*?\\]',"", txt) #ok
  txt = sub("More on Genius.*","", txt)
  txt = gsub('\\[.*?\\]', '', txt)
  txt = gsub("\\n"," ", txt)
  txt = gsub("[()]", " ", txt)
  txt = tolower(txt)
  txt = decontracted(txt)
  txt = gsub("\\W+\\b", " ", txt)
  txt = gsub("\\d", " ", txt)
  
  stopwords_regex = paste(stopwords('en'), collapse = '\\b|\\b')
  stopwords_regex = paste0('\\b', stopwords_regex, '\\b')
  txt = stringr::str_replace_all(txt, stopwords_regex, '')

  my_stopwords <- c('ooh', 'yeah', "aint", "get", "got", "ayy")
  txt = stringr::str_replace_all(txt, my_stopwords, '')
   
  txt = str_trim(txt)
  txt = gsub("\\n"," ", txt)
  
  if(language == "en"){
    return(txt)
  }else if (language == "es"){
    txt <- function(x) {stri_trans_general(x, "Latin-ASCII")}
      return(txt) 
  }else{
        return("Falta definir lenguaje")
      }
}

#función para obtener oraciones de una sola palabra. 
one_word_setences = function(txt){
  return(gsub("\\W+\\b", ". ", txt))
}

#limpio las letras en ingles
en_lyrics$lyrics = text_cleaning(en_lyrics$lyrics, language = "en")

head(en_lyrics$lyrics, 1)

Explicit

Español

#Diccionario español
malas_palabras_1 <- read_csv("data/malas_palabras.txt", 
    col_names = FALSE)

malas_palabras_2 <- read_csv("data/malas_palabras_translate.txt", 
    col_names = FALSE)

malas_palabras_3 <- read_csv("data/malas_palabras_wiki.txt", 
    col_names = FALSE) %>% 
  select(X1)

malas_palabras_4 <- read_csv("data/palabras_profanas_es.txt", 
                             col_names = FALSE)

malas_palabras <- rbind(malas_palabras_1, malas_palabras_2,
                        malas_palabras_3, malas_palabras_4)


#Función para limpiar. 
text_cleaning_esp = function(txt, stop=FALSE){
  txt = sub('^.+?\\[.*?\\]',"", txt) #ok
  txt = sub("More on Genius.*","", txt)
  txt = gsub('\\[.*?\\]', '', txt)
  txt = gsub("\\n"," ", txt)
  txt = gsub("[()]", " ", txt)
  txt = tolower(txt)
  # txt = decontracted(txt)
  txt = gsub("\\W+\\b", " ", txt)
  txt = gsub("\\d", " ", txt)
  txt = str_trim(txt)
  # txt = stri_trans_general(txt, "Latin-ASCII")
  return(txt)
}


malas_palabras$limpias = text_cleaning(malas_palabras$X1)
malas_palabras

malas_palabras %>% filter(startsWith(limpias, "g"))

Inglés

#Genero lista de malas palabras
bad_words <- c()
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_zac_anger)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_alvarez)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_arr_bad)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_racist)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_banned)))
bad_words <- unique(bad_words)

biglou <- read.csv("https://www.cs.cmu.edu/~biglou/resources/bad-words.txt", header=FALSE, col.names = c("words"))


#Función para obtener palabras profanas de cada lyric
get_profanities = function(txt, profanity_lst){
  # txt = text_cleaning(txt)
  words = as.data.frame(strsplit(txt, "[ ]+"), col.names = "words")
  profan_df = profanity(get_sentences(words), profanity_list = profanity_lst)
  profan_words = profan_df[profan_df$profanity_count!=0,]$words
  vector = as.vector(profan_words)
  if (length(vector)==0){
    return(NULL)
  }
  else{return(as.vector(profan_words))
    }
}



en_lyrics$profabe_biglou <- lapply(en_lyrics$lyrics,  function(x) get_profanities(x, biglou$words))

en_lyrics %>% 
  mutate(profane_biglou = unlist(get_profanities(lyrics, biglou$words)))

en_lyrics$profabe_biglou = unlist(strsplit(en_lyrics$profabe_biglou, split = " "))

en_lyrics$profabe_badwords <- lapply(en_lyrics$lyrics, function(x) get_profanities(x, bad_words))

str(en_lyrics)
head(en_lyrics,1)

en_lyrics$profabe_biglou[3]

MATRIZ TERMINO DOCUMENTO

####################################################################
####### Generación de la Matríz Término-Documento del corpus #######
####################################################################
corpus.pro2tdm <- function(corpus, ponderacion, n_terms){
  #corpus
  
  
  #matriz TD 
  dtm <- TermDocumentMatrix(corpus,
                            control = list(weighting = ponderacion))
  matriz_td <- as.matrix(dtm)
  
  
  # Calculamos la frecuencia de cada término en el corpus
  freq_term <- head(sort(rowSums(matriz_td),decreasing=TRUE), n_terms)
  
  #matriz transpuesta de los n_terms mas frecuentes
  matriz_nf <- t(matriz_td[sort(names(freq_term)), ])
  
  #pasaje a binario
  matriz_nf[matriz_nf>0] <- 1
  
  return(matriz_nf)
  
  }
  
corpus_eng = Corpus(VectorSource(enc2utf8(en_lyrics$lyrics)))
matriz <- corpus.pro2tdm(corpus = corpus_eng, ponderacion= "weightTf",n_terms= 150)

dim(matriz)

df_tm <- as.data.frame(matriz)
head(df_tm,2)

## Join matriz de palabras con artista y track
df_ly_feat <- cbind(df_lyrics_seleccionado[-c(3)], df_tm)

nrow(df_tm)
nrow(df_lyrics_seleccionado)
nrow(df_ly_feat)

filter <- !names(df_ly_feat) %in% c("artist_name", "track_name" )

df_ly_feat_ok <- df_ly_feat[, filter]
# df_ly_feat_ok = df_ly_feat_ok[, -(which(colSums(df_ly_feat_ok) == 0))]

# colSums(df_ly_feat_ok)

head(df_ly_feat_ok, 3)
head(df_ly_feat, 3)


df_ly_feat$id = 1:nrow(df_ly_feat)

df_melt <- reshape2::melt(data = df_ly_feat[,3:ncol(df_ly_feat)], id.vars = c("id"))  %>%
  arrange(id)

df_melt <- df_melt[df_melt$value != 0,]

df_melt_txt <- df_melt[df_melt$value == 1,]
df_melt_cat <- df_melt[df_melt$value != 1,]

head(df_melt_txt )
dim(df_melt_txt )

#denomino a los términos profanos
df_melt_txt <- df_melt_txt %>% 
  mutate(variable = case_when(as.character(variable) %in% biglou$words ~
                                paste0("PROF_", as.character(variable)),
                              T ~ paste0("TERM_", as.character(variable))
                              )  
         )

df_melt_txt %>% filter(startsWith(variable, "PROF"))


# df_melt_txt[df_melt_txt$variable %in% biglou$words,]


df_melt_txt_to_ruls <- df_melt_txt[, -c(3)]
names(df_melt_txt_to_ruls) <- c("id", "item")

write.table(df_melt_txt_to_ruls, file="data/transaccions_lyrics_features.txt", row.names = F)

# Reglas
# chequear nan's
lyrics_trans <- read.transactions("data/transaccions_lyrics_features.txt", format = "single", cols = c(1,2))

arules::inspect(head(lyrics_trans, 3))

summary(lyrics_trans)
reglas <- apriori(lyrics_trans, parameter = list(support=0.1,
                    confidence = 0.5, target  = "rules"  ))

reglas_sub <- subset(reglas, subset = rhs %pin% "PROF_")
arules::inspect(head(sort(reglas_sub, by = "lift", decreasing = T),5))

Carga de Igal (UNIFICAR)

Explicit

contar malas palabras (Parte de Igal: UNIFICAR)


bad_words <- c()
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_zac_anger)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_alvarez)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_arr_bad)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_racist)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_banned)))

bad_words <- unique(bad_words)


contar_bad_words <- function(x){
  x <- profanity(x,profanity_list = bad_words)
  q <- sum(x$profanity_count)
  return (q)
  }
df_chart_w_lyrics$cant_bad_words <- sapply(df_chart_w_lyrics[,"lyrics"], contar_bad_words)


df_chart_w_lyrics_only_explicit <- df_chart_w_lyrics[df_chart_w_lyrics$explicit==TRUE & df_chart_w_lyrics$cant_bad_words > 0, ]

hist(df_chart_w_lyrics_only_explicit$cant_bad_words)


#creo vars categóricas
df_chart_w_lyrics_only_explicit$nivel_puteada <- cut(df_chart_w_lyrics_only_explicit$cant_bad_words, breaks = c(0,10,20,50,Inf), labels=c("bajo","poco","alto","muy_alto"))

df_chart_w_lyrics_only_explicit$nivel_ranking <- cut(df_chart_w_lyrics_only_explicit$position_avg, breaks = c(1,100,Inf), labels=c("1a100","100a200"))

df_chart_w_lyrics_only_explicit$nivel_popularidad <- cut(sqrt(df_chart_w_lyrics_only_explicit$cant_bad_words), breaks = c(0,10,20,50,Inf), labels=c("bajo","poco","alto","muy_alto"))

transactions <- as(as.data.frame(apply(df_chart_w_lyrics_only_explicit, 2, as.factor)), "transactions")
rules = apriori(transactions, parameter=list(target="rules", confidence=0.25, support=0.1))
rules.sub <- subset(rules, subset = lhs %pin% "nivel_puteada" & rhs %pin% "nivel_ranking")
inspect(head(sort(rules.sub, by = "lift", decreasing = TRUE),10))

# discretizacion continuas y seleccion de variables
# identificar palabras explít

LIMPIO HASTA ACA

Preguntas de investigacion

Patron Comun Canciones del Chart

¿Qué características tienen las canciones que están en el chart? ¿Cual es el patrón comun que tienen las canciones más escuchadas? (ver dispersiones, media, grafico comparativo)



#funcion para escalar variable
scale_vble <- function(x){
  (x - mean(x, na.rm = T))/sd(x, na.rm = T)
}
#anti_join
anti_join_audio_charts <- df_audio_features %>% 
  select("artist_name","artist_all", "artist_key",
         "track_name", "external_urls_spotify", "album_name", "album_release_year",
         all_of(features_continuas), all_of(features_categoricas)) %>% 
  anti_join( df_charts %>%
               select( "Track_Name", "Artist", "URL"),
               by = c("external_urls_spotify" ="URL",
                      "artist_key" ="Artist"  ))
               # by = c("track_name" = "Track_Name"))


anti_join_audio_charts_complete <- na.omit(anti_join_audio_charts)
anti_join_audio_charts_complete_scale <- anti_join_audio_charts_complete %>% 
  distinct() %>% 
  select(features_continuas)  %>% 
  mutate_all(scale_vble)
nrow(anti_join_audio_charts_complete_scale)

Qué temas perduran mucho en el ranking

Artistas que mas aparecen en el chart

join_audio_charts %>% 
  group_by(artist_name) %>% 
  dplyr::summarise(n = n()) %>% 
  arrange(-n)

Tracks que mas aparecen en el chart

join_audio_charts %>% 
  group_by(track_name, artist_name,external_urls_spotify) %>% 
  dplyr::summarise(n = n()) %>% 
  arrange(-n) %>% 
  select(track_name, n, everything(.))

¿Cuánto tiempo están en un chart?

# cantidad de semanas que estuvieron en el chart

df_charts %>% 
  mutate(week_start=as.Date(week_start),
         week_end = as.Date(week_end),
         week_year = (year(week_start))) %>%
  arrange(Artist, Track_Name) %>% 
  group_by(Artist, Track_Name, URL) %>% 
 dplyr:: summarise( day_in = min(week_start),
             year_in = year(day_in),
             day_max = max(week_end),
             year_max = year(day_max),
             duracion_chart_dias = day_max-day_in,
             duracion_chart_anio = year_max - year_in) %>% 
  arrange(Artist)
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIExJQlJFUklBUw0KYGBge3IsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzcWxkZikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoc2VudGltZW50cikNCmxpYnJhcnkoYXJ1bGVzKQ0KYGBgDQoNCiMgQ0FSR0EgREUgREFUT1MgIA0KYGBge3J9DQpkZl9hcnRpc3QgPC0gcmVhZC5jc3YoImRhdGEvZGZfYXJ0aXN0X3Npbl9kdXBsaWNhZG9zLmNzdiIpDQpkZl9jaGFydHNfcmF3IDwtIHJlYWQuY3N2KCJkYXRhL2RmX2NoYXJ0c19zaW5fZHVwbGljYWRvcy5jc3YiKQ0KZGZfYXVkaW9fZmVhdHVyZXNfcmF3IDwtIHJlYWQuY3N2KCJkYXRhL2F1ZGlvX2ZlYXR1cmVzX3BsYW5vX3Npbl9kdXBsaWNhZG9zLmNzdiIpDQpkZl9seXJpY3MgPC0gcmVhZC5jc3YoImRhdGEvZGZfbHlyaWNzLmNzdiIpDQoNCmBgYA0KDQoNCiMjIENvcnJlY2Npw7NuIGR1cGxpY2Fkb3MNCmBgYHtyfQ0KIyBERiBsaXN0byBwYXJhIGVsIGpvaW4gY29uIGNocmF0cw0KZGZfYXVkaW9fZmVhdHVyZXMgPC0gZGZfYXVkaW9fZmVhdHVyZXNfcmF3ICU+JSANCiAgZ3JvdXBfYnkodHJhY2tfbmFtZSwgZXh0ZXJuYWxfdXJsc19zcG90aWZ5KSAlPiUgDQogIG11dGF0ZShhcnRpc3RfYWxsID0gcGFzdGUoYXJ0aXN0X25hbWUsIGNvbGxhcHNlID0gIix8LCIpKSAlPiUNCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKGFydGlzdF9rZXkgPSBzdWIoIix8LC4qIiwgIiIsIGFydGlzdF9hbGwpKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoYXJ0aXN0X25hbWUsIGFydGlzdF9hbGwsIGFydGlzdF9rZXksIGV2ZXJ5dGhpbmcoLikpICU+JSANCiAgZGlzdGluY3QoYXJ0aXN0X2tleSwgZXh0ZXJuYWxfdXJsc19zcG90aWZ5LCAua2VlcF9hbGwgPSBUKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKQ0KYGBgDQoNCiMjIENyZWFjaW9uIGBjYW50X21hcmtldHNgDQpgYGB7cn0NCmNvbnRhcl9tYXJrZXQgPC0gZnVuY3Rpb24oeCl7DQpxIDwtIGxlbmd0aCh1bmxpc3Qoc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLCIpKSkNCnJldHVybiAocSkNCiAgfQ0KZGZfYXVkaW9fZmVhdHVyZXMkY2FudF9tYXJrZXRzIDwtIHNhcHBseShkZl9hdWRpb19mZWF0dXJlc1ssIm1hcmtldHNfY29uY2F0Il0sIGNvbnRhcl9tYXJrZXQpDQpgYGANCg0KDQojIyBWZWN0b3JlcyBkZSBmZWF0dXJlcw0KYGBge3J9DQojZmVhdHVyZXMgdmFyIGNvbnRpbnVvcw0KZmVhdHVyZXNfY29udGludWFzIDwtIGMoJ2Fjb3VzdGljbmVzcycsICdkYW5jZWFiaWxpdHknLCAnZHVyYXRpb25fbXMnLCAnZW5lcmd5JywgJ2luc3RydW1lbnRhbG5lc3MnLCAnbGl2ZW5lc3MnLCAnbG91ZG5lc3MnLCAnc3BlZWNoaW5lc3MnLCAgICd0ZW1wbycsICd2YWxlbmNlJywgJ2NhbnRfbWFya2V0cycpDQoNCiNmZWF0dXJlcyB2YXJfIGNhdGVnw7NyaWNhcw0KZmVhdHVyZXNfY2F0ZWdvcmljYXMgPC0gYygnZXhwbGljaXQnLCAna2V5X25hbWUnLCAnbW9kZV9uYW1lJywgImtleV9tb2RlIikNCg0KYGBgDQoNCg0KIyBDSEFSVFM6IGFncmVnYWNpw7NuIGRlIGZlYXR1cmVzDQpgYGB7cn0NCiNtZXRyaWNhIGRlIHBvcHVsYXJpZGFkDQpkZl9jaGFydHMgPC0gZGZfY2hhcnRzX3JhdyAlPiUgDQogIGdyb3VwX2J5KEFydGlzdCwgVHJhY2tfTmFtZSwgVVJMKSAlPiUNCiAgZHBseXI6OiBzdW1tYXJpc2Uoc2VtYW5hc19zdW0gPSBuKCksDQogICAgICAgICAgICBzdHJlYW1zX3N1bSA9IChzdW0oU3RyZWFtcywgbmEucm0gPSBUKS8xMF42ICksDQogICAgICAgICAgICBzdHJlYW1zX21pbiA9IChtaW4oU3RyZWFtcykvMTBeNiApLA0KICAgICAgICAgICAgc3RyZWFtc19tYXggPSAobWF4KFN0cmVhbXMpLzEwXjYgKSwNCiAgICAgICAgICAgIHBvc2l0aW9uX2F2ZyA9IG1lYW4oUG9zaXRpb24sIG5hLnJtID0gVCksDQogICAgICAgICAgICBwb3NpdGlvbl9taW4gPSBtaW4oUG9zaXRpb24pLCANCiAgICAgICAgICAgIHBvc2l0aW9uX21heCA9IG1heChQb3NpdGlvbikpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKHBvcHVsYXJpZGFkID0gYXMubnVtZXJpYyhzdHJlYW1zX3N1bSpzZW1hbmFzX3N1bS9wb3NpdGlvbl9hdmcpICkNCg0KYGBgDQoNCg0KDQojIyBBZ3JlZ2FjacOzbiBkZSB0b2RhcyBsYXMgc2VtYW5hcyBlbiBjaGFydHMNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KDQpncm91cHBpbmdfY29scyA8LSBjKCJhcnRpc3RfbmFtZSIsImFydGlzdF9hbGwiLCJhcnRpc3Rfa2V5IiwNCiAgICAgICAgICAgICAgICAgICAgInRyYWNrX25hbWUiLCJleHRlcm5hbF91cmxzX3Nwb3RpZnkiLCJhbGJ1bV9uYW1lIiwiYWxidW1fcmVsZWFzZV95ZWFyIikNCg0KbnVtZXJpY19jb2xfY2hhcnRzIDwtIGMoIlBvc2l0aW9uIiwiU3RyZWFtcyIpDQoNCndlZWtfc3RhcnQgPC0gYygid2Vla19zdGFydCIpDQoNCmNoYXJ0X2dyb3VwIDwtIGpvaW5fYXVkaW9fY2hhcnRzICU+JSANCiAgICAgICAgICAgICAgICBncm91cF9ieShhcnRpc3RfbmFtZSxhcnRpc3RfYWxsLGFydGlzdF9rZXksdHJhY2tfbmFtZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBleHRlcm5hbF91cmxzX3Nwb3RpZnksYWxidW1fbmFtZSxhbGJ1bV9yZWxlYXNlX3llYXIpDQoNCg0KY29udGludWFzX3N1bW1hcml6ZWQgPSBjaGFydF9ncm91cCAlPiUgc3VtbWFyaXNlX2F0KGZlYXR1cmVzX2NvbnRpbnVhcywgbWVhbiwgbmEucm0gPSBUUlVFKQ0KY2F0ZWdvcmljYXNfc3VtbWFyaXplcyA9IGNoYXJ0X2dyb3VwICU+JSBzdW1tYXJpc2VfYXQoZmVhdHVyZXNfY2F0ZWdvcmljYXMsIGZpcnN0KQ0KbnVtZXJpY19jaGFydHNfc3VtbWFyaXplcyA9IGNoYXJ0X2dyb3VwICU+JSBzdW1tYXJpc2UoYWNyb3NzKG51bWVyaWNfY29sX2NoYXJ0cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KG1pbj1taW4sbWF4PW1heCxhdmc9bWVhbikpKQ0KDQpjYW50X3NlbWFuYXMgPSBjaGFydF9ncm91cCAlPiUgc3VtbWFyaXNlX2F0KHdlZWtfc3RhcnQsIG5fZGlzdGluY3QpDQpuYW1lcyhjYW50X3NlbWFuYXMkd2Vla19zdGFydCkgPC0gImNhbnRfc2VtYW5hcyINCg0KYWdncmVnYXRpb25fZGYgPC0gY2JpbmQobnVtZXJpY19jaGFydHNfc3VtbWFyaXplcywNCiAgICAgICAgICAgICAgICAgICAgICAgIGNhbnRfc2VtYW5hc1ssLWMoMTo3KV0sY29udGludWFzX3N1bW1hcml6ZWRbLC1jKDE6NyldLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGNhdGVnb3JpY2FzX3N1bW1hcml6ZXNbLC1jKDE6NyldKQ0KDQpuYW1lcyhhZ2dyZWdhdGlvbl9kZilbbmFtZXMoYWdncmVnYXRpb25fZGYpID09ICd3ZWVrX3N0YXJ0J10gPC0gImNhbnRfc2VtYW5hcyINCg0KY29scyA8LSBuYW1lcyhhZ2dyZWdhdGlvbl9kZikNCm51bWVyaWNfY29scyA8LSBjb2xzW3NhcHBseShhZ2dyZWdhdGlvbl9kZixpcy5udW1lcmljKV0NCg0Kc3VtbWFyeShhZ2dyZWdhdGlvbl9kZlssbnVtZXJpY19jb2xzWzI6bGVuZ3RoKG51bWVyaWNfY29scyldXSkNCg0KDQpgYGANCg0KDQoNCg0KIyBSSUdUSCBKT0lOIGBhdWRpb19mZWF0dXJlc2AgWSBgY2hhcnRzYA0KYGBge3J9DQojQXJtYW1vcyB1biBqb2luIHBhcmEgdGVuZXIgdW5hIHRhYmxhIGRlIGNoYXJ0cyBjb24gbGFzIGNhcmFjdGVyaXN0aWNhcyBkZSBsYXMgY2FuY2lvbmVzDQojIGRlYmVyaWFuIHF1ZWRhciAyMjk5MyBmaWxhcyBjb21wbGV0YXMNCmpvaW5fYXVkaW9fY2hhcnRzIDwtIGRmX2F1ZGlvX2ZlYXR1cmVzICU+JSANCiAgc2VsZWN0KCJhcnRpc3RfbmFtZSIsImFydGlzdF9hbGwiLCJhcnRpc3Rfa2V5IiwNCiAgICAgICAgICJ0cmFja19uYW1lIiwgImV4dGVybmFsX3VybHNfc3BvdGlmeSIsICJhbGJ1bV9uYW1lIiwgImFsYnVtX3JlbGVhc2VfeWVhciIsDQogICAgICAgICBhbGxfb2YoZmVhdHVyZXNfY29udGludWFzKSwgYWxsX29mKGZlYXR1cmVzX2NhdGVnb3JpY2FzKSkgJT4lIA0KICByaWdodF9qb2luKCBkZl9jaGFydHMsIyAlPiUNCiAgICAgICAgICAgICAgIGJ5ID0gYygNCiAgICAgICAgICAgICAgICAgInRyYWNrX25hbWUiID0gIlRyYWNrX05hbWUiLCANCiAgICAgICAgICAgICAgICAgICAgICAiYXJ0aXN0X2tleSIgPSJBcnRpc3QiLCANCiAgICAgICAgICAgICAgICAgICAgICAiZXh0ZXJuYWxfdXJsc19zcG90aWZ5IiA9ICJVUkwiKSkNCg0KI0hBWSBDSEFSVFMgUVVFIE5PIFRJRU5FTiBGRUFUVVJFUy4gSEFZIFFVRSBURU5FUkxPIEVOIENVRU5UQSBQQVJBIEVMIEFOw4FMSVNJUw0KbGlicmFyeShtaWNlKQ0KbWQucGF0dGVybihqb2luX2F1ZGlvX2NoYXJ0cywgcm90YXRlLm5hbWVzID0gVFJVRSkNCg0KYGBgDQoNCg0KDQojIEhJU1RPR1JBTUFTIFkgQkFSUExPVFMgREUgVkFSSUFCTEVTDQpgYGB7cn0NCg0KIyNoaXN0b2dyYW1hIGRlIGxhcyB2YXJpYWJsZXMgY29udGludWFzIGRlIGF1ZGlvX2ZlYXR1cmVzDQoNCmZvciAoaSBpbiBmZWF0dXJlc19jb250aW51YXMpew0KDQogIGhpc3QoZGZfYXVkaW9fZmVhdHVyZXNbLGldLCBtYWluID0gcGFzdGUoIkhpc3RvZ3JhbWEgZGUiLCBpLCAiKGFsbCBkYXRhKSIpLCB4bGFiID0gaSkNCiAgYWJsaW5lKHYgPSBtZWFuKGRmX2F1ZGlvX2ZlYXR1cmVzWyxpXSwgbmEucm0gPSBUUlVFKSAsIGNvbD0icmVkIikNCiAgYWJsaW5lKHYgPSBtZWRpYW4oZGZfYXVkaW9fZmVhdHVyZXNbLGldLCBuYS5ybSA9IFRSVUUpICwgY29sPSJibHVlIikNCiAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IGMoIk1lZGlhIiwgIk1lZGlhbmEiKSwgY29sPWMoInJlZCIsICJibHVlIiksIGx0eSA9MSkNCg0KfQ0KDQojZGl2aWRvIGxvcyBmZWF0dXJlcyBwb3Igc3UgZGlzdHJpYnVjacOzbg0KZmVhdHVyZXNfY29udGludWFzX21lZGlhIDwtIGMoJ2RhbmNlYWJpbGl0eScsICd0ZW1wbycsICd2YWxlbmNlJykNCg0KZmVhdHVyZXNfY29udGludWFzX21lZGlhbmEgPC0gYygnYWNvdXN0aWNuZXNzJywgJ2R1cmF0aW9uX21zJywgJ2VuZXJneScsICdpbnN0cnVtZW50YWxuZXNzJywgJ2xpdmVuZXNzJywgJ2xvdWRuZXNzJywgJ3NwZWVjaGluZXNzJywgJ2NhbnRfbWFya2V0cycpDQoNCg0KIyNoaXN0b2dyYW1hIGRlIGxhcyB2YXJpYWJsZXMgY29udGludWFzIGRlIGNoYXJ0cw0KZm9yIChpIGluIGMoZmVhdHVyZXNfY29udGludWFzKSl7DQoNCiAgaGlzdChqb2luX2F1ZGlvX2NoYXJ0c1ssaV0sIG1haW4gPSBwYXN0ZSgiSGlzdG9ncmFtYSBkZSIsIGksICAiKGNoYXJ0cykiKSwgeGxhYiA9IGkpDQogIGFibGluZSh2ID0gbWVhbihqb2luX2F1ZGlvX2NoYXJ0c1ssaV0sIG5hLnJtID0gVFJVRSkgLCBjb2w9InJlZCIpDQogIGFibGluZSh2ID0gbWVkaWFuKGpvaW5fYXVkaW9fY2hhcnRzWyxpXSwgbmEucm0gPSBUUlVFKSAsIGNvbD0iYmx1ZSIpDQoNCn0NCg0KI2RpdmlkbyBmZWF0dXJlcyBkZSBjaGFydHMgc2Vnw7puIHN1IGRpc3RyaWJ1Y2nDs24NCmF1ZGlvX2NoYXJ0c19jb250aW51YXNfbWVkaWEgPC0gYygnZHVyYXRpb25fbXMnLCAndmFsZW5jZScpDQoNCmF1ZGlvX2NoYXJ0c19jb250aW51YXNfbWVkaWFuYSA8LSBjKCdkYW5jZWFiaWxpdHknLCAnYWNvdXN0aWNuZXNzJywgJ3RlbXBvJywgJ2VuZXJneScsICdpbnN0cnVtZW50YWxuZXNzJywgJ2xpdmVuZXNzJywgJ2xvdWRuZXNzJywgJ3NwZWVjaGluZXNzJywgJ2NhbnRfbWFya2V0cycsICJTdHJlYW1zIikNCg0KDQojI21lZGlkYXMgcmVzdW1lbiB5IGJhcnBsb3RzIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWdvcmljYXMgYXVkaW9fZmVhdHVyZXMNCmZvcihpIGluIGZlYXR1cmVzX2NhdGVnb3JpY2FzKXsNCg0KICBiYXJwbG90KHNvcnQodGFibGUoZGZfYXVkaW9fZmVhdHVyZXNbLGldKSxkZWNyZWFzaW5nID0gVCksIGxhcz0yLCANCiAgICAgICAgICBtYWluID0gcGFzdGUoIkJhcnBsb3QgZGUiLCBpLCAiKGFsbCBkYXRhKSIpKQ0KICAjIHBpZSh0YWJsZShkZl9mZWF0dXJlc19jYXRlZ29yaWNvc1ssaV0pKQ0KfQ0KDQoNCg0KIyNtZWRpZGFzIHJlc3VtZW4geSBiYXJwbG90cyBkZSBsYXMgdmFyaWFibGVzIGNhdGVnb3JpY2FzIGpvaW5fYXVkaW9fY2hhcnRzDQoNCmZvcihpIGluIGZlYXR1cmVzX2NhdGVnb3JpY2FzKXsNCiAgYmFycGxvdCh0YWJsZShqb2luX2F1ZGlvX2NoYXJ0c1ssaV0pLCBsYXM9MiwNCiAgICAgICAgICBtYWluID0gcGFzdGUoIkJhcnBsb3QgZGUiLCBpLCAiKGNoYXJ0cykiKQ0KICAgICAgICAgICkNCiAgIyBwaWUodGFibGUoZGZfZmVhdHVyZXNfY2F0ZWdvcmljb3NbLGldKSkNCn0NCg0KYGBgDQoNCg0KDQojIFNFU0dPIERFIFZBUklBQkxFUyANCg0KDQojIyBCb3hwbG90cyBWYXJpYWJsZXMgTnVtw6lyaWNhcyBzaW4gZmlsdHJhciBvdXRsaWVycw0KYGBge3J9DQpwYXIobWZyb3c9Yyg0LDMpKQ0KZm9yIChmZWF0dXJlIGluIGZlYXR1cmVzX2NvbnRpbnVhcyl7DQogIGJveHBsb3QoZGZfYXVkaW9fZmVhdHVyZXNbLGZlYXR1cmVdLCBsYXM9MiwgaG9yaXpvbnRhbD1ULCBtYWluPWZlYXR1cmUpDQp9DQpgYGANCg0KQ29uIGV4Y2VwY2nDs24gZGUgdmFsZW5jZSBlbCByZXN0byBkZSBsYXMgZmVhdHVyZXMgcG9zZcOtYW4gY2llcnRvIHNlc2dvLiBTZSBkZWNpZGnDsyB0cmFuc2Zvcm1hciBsYXMgdmFyaWFibGVzIHF1ZSBtYXlvciBzZXNnbyBwb3Nlw61hbjogZHVyYXRpb25fbXMsIGluc3RydW1lbnRhbG5lc3MsIGxpdmVuZXNzLCBzcGVlY2hpbmVzcyBjb21vIG3DqXRvZG8gZGUgY29ycmVnaXIgbGEgZGlzdHJpYnVjacOzbiB5IGFjaGljYXIgbGEgY2FudGlkYWQgZGUgb3V0bGllcnMuIExhIHZhcmlhYmxlIGxvdWRuZXNzX3JlZ19pbXAgbm8gZnVlIG1vZGlmaWNhZGEgZGViaWRvIGEgcXVlIGFsIHNlciBuZWdhdGl2YSANCg0KIyMgVHJhbnNmb3JtYWNpb25lcw0KDQojIyMgVHJhbnNmb3JtYWNpw7NuIGxvZ2Fyw610bWljYQ0KYGBge3J9DQojICJkYW5jZWFiaWxpdHksdGVtcG8sdmFsZW5jZSxhY291c3RpY25lc3MsZHVyYXRpb25fbXMsZW5lcmd5LGluc3RydW1lbnRhbG5lc3MsbGl2ZW5lc3Msc3BlZWNoaW5lc3MsY2FudF9tYXJrZXRzIg0KDQojc2VzZ29zIGQgbGFzIHZhcmlhYmxlcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0Kc29ydChhcHBseShkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZXNfY29udGludWFzXSwgTUFSR0lOID0gMiwgZnVuY3Rpb24oeCl7ICgzKiAobWVhbih4LG5hLnJtID0gVCktbWVkaWFuKHgsIG5hLnJtID0gVCkpKS9zZCh4LCBuYS5ybSA9IFQpfSApKQ0KDQp2YXJpYWJsZXNfc2VzZ28gPC0gdW5saXN0KHN0cnNwbGl0KCJhY291c3RpY25lc3MsZHVyYXRpb25fbXMsaW5zdHJ1bWVudGFsbmVzcyxsaXZlbmVzcyxzcGVlY2hpbmVzcyxjYW50X21hcmtldHMsZW5lcmd5IiwgIiwiKSkNCg0KZGZfc2VzZ2FkYXMgPC0gZGZfYXVkaW9fZmVhdHVyZXNbLHZhcmlhYmxlc19zZXNnb10NCg0KbG9nYXJpdG1vX2FqdXN0YWRvID0gZnVuY3Rpb24oeCxkZWx0YSl7DQogIGlmICh4PT0wLjApew0KICAgIHJldHVybihsb2coMC4wMCtkZWx0YSwgYmFzZSA9IDEwKSkNCiAgfWVsc2V7DQogICAgcmV0dXJuKGxvZyh4LCBiYXNlID0gMTApKQ0KICB9DQp9DQoNCmRlbHRhIDwtIDEwXigtNikNCg0KZGZfc2VzZ2FkYXNfbG9nX2FkanVzdCA8LSBkYXRhLmZyYW1lKGFwcGx5KGRmX2F1ZGlvX2ZlYXR1cmVzWyx2YXJpYWJsZXNfc2VzZ29dLCBNQVJHSU4gPSBjKDEsMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIGxvZ2FyaXRtb19hanVzdGFkbyh4LGRlbHRhKSkpDQoNCmdncGxvdChtZWx0KGRmX3Nlc2dhZGFzX2xvZ19hZGp1c3QpLCBhZXModmFsdWUpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oKSsNCiAgZmFjZXRfd3JhcCh+dmFyaWFibGUpDQoNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBuYW1lcyhkZl9zZXNnYWRhc19sb2dfYWRqdXN0KSA8LSBwYXN0ZShuYW1lcyhkZl9zZXNnYWRhcyksICJfbG9nIiwgc2VwPSIiKQ0KbmFtZXMoZGZfc2VzZ2FkYXNfbG9nX2FkanVzdCkgPC0gbmFtZXMoZGZfc2VzZ2FkYXMpDQoNCmRmX2RhdG9zIDwtIGNiaW5kKGRmX3Nlc2dhZGFzLCBkZl9zZXNnYWRhc19sb2dfYWRqdXN0KQ0KDQoNCg0KYSA8LSBkZl9zZXNnYWRhcw0KYiA8LSBkZl9zZXNnYWRhc19sb2dfYWRqdXN0DQpuYW1lcyhiKSA8LSBwYXN0ZShuYW1lcyhkZl9zZXNnYWRhcyksICJfbG9nIiwgc2VwPSIiKQ0KbWVyZ2VkIDwtIGNiaW5kKGEsYikNCg0KbWVyZ2VkIDwtIG1lcmdlZFssIG9yZGVyKG5hbWVzKG1lcmdlZCkpXQ0KDQpyb3VuZChzb3J0KGFwcGx5KG1lcmdlZCwgTUFSR0lOID0gMiwgZnVuY3Rpb24oeCl7ICgzKiAobWVhbih4LG5hLnJtID0gVCktbWVkaWFuKHgsIG5hLnJtID0gVCkpKS9zZCh4LCBuYS5ybSA9IFQpfSkpLDIpDQoNCmBgYA0KDQpgYGB7cn0NCiNqb2luIGVudHJlIHZhcmlhYmxlcyB0cmFuc2Zvcm1hZGFzIHkgcmVzdG8gZmVhdHVyZXMNCnggPC0gZGZfYXVkaW9fZmVhdHVyZXMgJT4lIA0KICBzZWxlY3QoImFydGlzdF9uYW1lIiwiYXJ0aXN0X2FsbCIsImFydGlzdF9rZXkiLA0KICAgICAgICAgInRyYWNrX25hbWUiLCAiZXh0ZXJuYWxfdXJsc19zcG90aWZ5IiwgImFsYnVtX25hbWUiLCAiYWxidW1fcmVsZWFzZV95ZWFyIiwNCiAgICAgICAgIGFsbF9vZihmZWF0dXJlc19jb250aW51YXMpLCBhbGxfb2YoZmVhdHVyZXNfY2F0ZWdvcmljYXMpKSAlPiUNCiAgc2VsZWN0KCF2YXJpYWJsZXNfc2VzZ28pIA0KDQpqb2luX2F1ZGlvX2NoYXJ0cyA8LSBjYmluZCh4LCBkZl9zZXNnYWRhc19sb2dfYWRqdXN0KSAlPiUgDQogIHJpZ2h0X2pvaW4oIGRmX2NoYXJ0cyAlPiUNCiAgICAgICAgICAgICAgIHNlbGVjdCggIlRyYWNrX05hbWUiLCAiQXJ0aXN0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICJVUkwiLCJQb3NpdGlvbiIsICJTdHJlYW1zIiwgIndlZWtfc3RhcnQiLCAid2Vla19lbmQiKSwNCiAgICAgICAgICAgICAgIGJ5ID0gYygidHJhY2tfbmFtZSIgPSAiVHJhY2tfTmFtZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICJhcnRpc3Rfa2V5IiA9IkFydGlzdCIsIA0KICAgICAgICAgICAgICAgICAgICAgICJleHRlcm5hbF91cmxzX3Nwb3RpZnkiID0gIlVSTCIpKQ0KYGBgDQoNCg0KYGBge3J9DQoNCnZhcmlhYmxlc19wbG90IDwtIHVubGlzdChzdHJzcGxpdCgiZHVyYXRpb25fbXMiLCAiLCIpKQ0KdmFyaWFibGVzX3Bsb3QgPC0gYXBwZW5kKHZhcmlhYmxlc19wbG90LCBwYXN0ZSh2YXJpYWJsZXNfcGxvdCwiX2xvZyIsIHNlcD0iIikpDQp2YXJpYWJsZXNfcGxvdCA8LSB2YXJpYWJsZXNfcGxvdFtvcmRlcih2YXJpYWJsZXNfcGxvdCldDQpwbG90ZWFyIDwtIG1lcmdlZFssdmFyaWFibGVzX3Bsb3RdDQoNCnBhcihtZnJvdyA9IGMoMSwyKSkNCmZvciAoY29sIGluIG5hbWVzKHBsb3RlYXIpKXsNCiAgaGlzdChwbG90ZWFyWyxjb2xdLCBicmVha3M9IkZEIiwgbWFpbj1jb2wsIHhsYWI9IiIpDQp9DQoNCmBgYA0KDQoNCg0KDQoNCmBgYHtyfQ0KDQp0cmFuc2Zvcm1hY2lvbiA8LSBjKCdpbnN0cnVtZW50YWxuZXNzJywnbG91ZG5lc3MnLCdsaXZlbmVzcycsJ3NwZWVjaGluZXNzJywgJ2R1cmF0aW9uX21zJykNCg0KbG9nYXJpdG1vX2FqdXN0YWRvID0gZnVuY3Rpb24oeCxkZWx0YSl7DQogIGlmICh4PD0wLjApew0KICAgIHJldHVybihsb2coMC4wMCtkZWx0YSwgYmFzZSA9IDEwKSkNCiAgfWVsc2V7DQogICAgcmV0dXJuKGxvZyh4LCBiYXNlID0gMTApKQ0KICB9DQp9DQoNCmRlbHRhIDwtIDEwXigtNikNCg0KcGFyKG1mcm93PWMoMiw1KSkNCmZvciAoZmVhdHVyZSBpbiB0cmFuc2Zvcm1hY2lvbil7DQogIGhpc3QoZGZfYXVkaW9fZmVhdHVyZXNbLGZlYXR1cmVdLCBtYWluPWZlYXR1cmUpDQp9DQoNCmZvciAoZmVhdHVyZSBpbiB0cmFuc2Zvcm1hY2lvbil7DQogIGhpc3QodW5saXN0KGxhcHBseShkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZV0sIGZ1bmN0aW9uKHgpIGxvZ2FyaXRtb19hanVzdGFkbyh4LGRlbHRhKSkpLCBtYWluPXBhc3RlKGZlYXR1cmUsImxvZyIsIHNlcD0iXyIpKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KDQppbnZfc3FydF9hanVzdGFkYSA9IGZ1bmN0aW9uKHgsIGRlbHRhKXsNCiAgaWYgKHg9PTAuMCl7DQogICAgcmV0dXJuKDEvc3FydCh4K2RlbHRhKSkNCiAgfWVsc2V7DQogICAgcmV0dXJuKDEvc3FydCh4KSkNCiAgfQ0KfQ0KDQoNCmRlbHRhIDwtIDEwXigtNikNCg0KcGFyKG1mcm93PWMoMiw1KSkNCmZvciAoZmVhdHVyZSBpbiB0cmFuc2Zvcm1hY2lvbil7DQogIGhpc3QoZGZfYXVkaW9fZmVhdHVyZXNbLGZlYXR1cmVdLCBtYWluPWZlYXR1cmUpDQp9DQpmb3IgKGZlYXR1cmUgaW4gdHJhbnNmb3JtYWNpb24pew0KICBoaXN0KHVubGlzdChsYXBwbHkoZGZfYXVkaW9fZmVhdHVyZXNbLGZlYXR1cmVdLCBmdW5jdGlvbih4KSBpbnZfc3FydF9hanVzdGFkYSh4LGRlbHRhKSkpLCBtYWluPXBhc3RlKGZlYXR1cmUsImludl9zcXQiLCBzZXA9Il8iKSkNCn0NCg0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQoNCnBhcihtZnJvdz1jKDIsNSkpDQpmb3IgKGZlYXR1cmUgaW4gdHJhbnNmb3JtYWNpb24pew0KICBoaXN0KGRmX2F1ZGlvX2ZlYXR1cmVzWyxmZWF0dXJlXSwgbWFpbj1mZWF0dXJlKQ0KfQ0KZm9yIChmZWF0dXJlIGluIHRyYW5zZm9ybWFjaW9uKXsNCiAgaGlzdChzcXJ0KGRmX2F1ZGlvX2ZlYXR1cmVzWyxmZWF0dXJlXSksIG1haW49cGFzdGUoZmVhdHVyZSwic3FydCIsIHNlcD0iXyIpKQ0KfQ0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpwYXIobWZyb3cgPSBjKDIsMSkpIA0KaGlzdChkZl9hdWRpb19mZWF0dXJlc1ssJ2xvdWRuZXNzX3JlZ19pbXAnXSwgbWFpbj0nbG91ZG5lc3MnLCB4bGFiPSIiKQ0KI2hpc3Qoc3FydChkZl9hdWRpb19mZWF0dXJlc1ssJ2xvdWRuZXNzX3JlZ19pbXAnXSksIG1haW49ICdsb3VkbmVzc19zcXJ0JywgeGxhYj0iIikNCmJveHBsb3QoZGZfYXVkaW9fZmVhdHVyZXNbLCdsb3VkbmVzc19yZWdfaW1wJ10sIGhvcml6b250YWwgPSBUKQ0KI2JveHBsb3Qoc3FydChkZl9hdWRpb19mZWF0dXJlc1ssJ2xvdWRuZXNzX3JlZ19pbXAnXSksIGhvcml6b250YWwgPSBUKQ0KDQoNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCmZpdCA8LSBsbShsb3VkbmVzc35lbmVyZ3krYWNvdXN0aWNuZXNzLCBkYXRhPWRmX2F1ZGlvX2ZlYXR1cmVzKQ0KDQptb2RlbG8gPC0gZml0JGNvZWZmaWNpZW50cw0KDQpkZl9hdWRpb19mZWF0dXJlcyRsb3VkbmVzc19yZWdfaW1wIDwtIGRmX2F1ZGlvX2ZlYXR1cmVzJGxvdWRuZXNzDQoNClggPC0gZGZfYXVkaW9fZmVhdHVyZXNbZGZfYXVkaW9fZmVhdHVyZXMkbG91ZG5lc3M+MCwgYygnZW5lcmd5JywgImFjb3VzdGljbmVzcyIpXQ0KDQpkZl9hdWRpb19mZWF0dXJlcyRsb3VkbmVzc19yZWdfaW1wW2RmX2F1ZGlvX2ZlYXR1cmVzJGxvdWRuZXNzPjBdIDwtIG1vZGVsb1sxXSttb2RlbG9bMl0qWFssMV0rbW9kZWxvWzNdKlhbLDJdDQoNCnN1bW1hcnkoZGZfYXVkaW9fZmVhdHVyZXNbLGMoImxvdWRuZXNzIiwgImxvdWRuZXNzX3JlZ19pbXAiKV0pDQoNCnN1bW1hcnkoZml0KQ0KYGBgDQoNCg0KDQpgaW5zdHJ1bWVudGFsbmVzc2AgdGllbmUgbXVjaG8gc2VzZ28gbGEgdmFyaWFibGUuIFNlIHZhIGEgcmVjdXJyaXIgYSB1bmEgbG9nYXJpdG1pemFjacOzbiBkZSBsYSB2YXJpYWJsZSwgcHJldmlhIHRyYW5zZm9ybWFjacOzbiBkZWwgZG9taW5pbywgaGFjaWVuZG8gcXVlIGxvcyB2YWxvcmVzIHF1ZSBzb24gMCwgc2VhbiBlbiByZWFsaWRhZCAwLjAwMDAwMDEgIA0KDQpgYGB7cn0NCmxvZ2FyaXRtb19hanVzdGFkbyA9IGZ1bmN0aW9uKHgsZGVsdGEpew0KICBpZiAoeD09MC4wKXsNCiAgICByZXR1cm4obG9nKHgrZGVsdGEsIGJhc2UgPSAxMCkpDQogIH1lbHNlew0KICAgIHJldHVybihsb2coeCwgYmFzZSA9IDEwKSkNCiAgfQ0KfQ0KDQpkZWx0YSA8LSAxMF4oLTYpDQoNCmRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3NfbG9nYWRqdXN0IDwtIHVubGlzdChsYXBwbHkoZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzcywgZnVuY3Rpb24oeCkgbG9nYXJpdG1vX2FqdXN0YWRvKHgsZGVsdGEpKSkNCg0KcGFyKG1mcm93ID1jKDIsMikpDQpoaXN0KGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3MsIG1haW49Imluc3J1bWVudGFsbmVzcyIsIHhsYWI9IiIpDQpoaXN0KHVubGlzdChsYXBwbHkoZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzcywgZnVuY3Rpb24oeCkgbG9nYXJpdG1vX2FqdXN0YWRvKHgsZGVsdGEpKSksIG1haW49J2luc3RydW1lbnRhbG5lc3NfbG9nYWRqdXN0JywgeWxpbSA9IGMoMCwxMzA1MDApLCB4bGFiID0gIiIpDQpib3hwbG90KGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3MsIG1haW49IiIsIGhvcml6b250YWwgPSBUKQ0KYm94cGxvdCh1bmxpc3QobGFwcGx5KGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3MsIGZ1bmN0aW9uKHgpIGxvZ2FyaXRtb19hanVzdGFkbyh4LGRlbHRhKSkpLCBtYWluPSIiLCBob3Jpem9udGFsPVQpDQojIGhpc3QobG9nKDEvc3FydChkZl9hdWRpb19mZWF0dXJlcyRpbnN0cnVtZW50YWxuZXNzKzAuMDAwMDEpKSxtYWluPSdsb2coc3FydCh4KykpJywgeWxpbT1jKDAsMTMwNTAwKSwgeGxhYiA9ICIiKQ0KDQpgYGANCg0Kwr9FcyDDunRpbCBlc3RhIHRyYW5zZm9ybWFjacOzbj8gDQoNCmBgYHtyfQ0KDQpkZWx0YSA8LSAxMF4oLTYpDQoNCmRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3NfbG9nYWRqdXN0IDwtIHVubGlzdChsYXBwbHkoZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzcywgZnVuY3Rpb24oeCkgbG9nYXJpdG1vX2FqdXN0YWRvKHgsZGVsdGEpKSkNCg0KZGZfY2hhcnRfdG9qb2luIDwtIGRmX2NoYXJ0c1ssYygiVHJhY2tfTmFtZSIsICJBcnRpc3QiLCAiVVJMIildDQpkZl9jaGFydF90b2pvaW4kaXNpbmNoYXJ0IDwtIDENCmRmX2F1ZGlvX2ZlYXR1cmVzX3Rvam9pbiA8LSBkZl9hdWRpb19mZWF0dXJlc1ssIGMoInRyYWNrX25hbWUiLCJhcnRpc3Rfa2V5IiwiZXh0ZXJuYWxfdXJsc19zcG90aWZ5IiwiaW5zdHJ1bWVudGFsbmVzcyIsICJpbnN0cnVtZW50YWxuZXNzX2xvZ2FkanVzdCIpXQ0KDQpqb2luX2hpc3RvZ3JhbSA8LSBkZl9hdWRpb19mZWF0dXJlc190b2pvaW4gJT4lIA0KICBkcGx5cjo6c2VsZWN0KCJ0cmFja19uYW1lIiwiYXJ0aXN0X2tleSIsImV4dGVybmFsX3VybHNfc3BvdGlmeSIsImluc3RydW1lbnRhbG5lc3MiLCAiaW5zdHJ1bWVudGFsbmVzc19sb2dhZGp1c3QiKSAlPiUgDQogIGxlZnRfam9pbiggZGZfY2hhcnRfdG9qb2luICU+JQ0KICAgICAgICAgICAgICAgc2VsZWN0KCJUcmFja19OYW1lIiwgIkFydGlzdCIsICJVUkwiLCJpc2luY2hhcnQiKSwNCiAgICAgICAgICAgICAgIGJ5ID0gYygNCiAgICAgICAgICAgICAgICAgInRyYWNrX25hbWUiID0gIlRyYWNrX05hbWUiLCANCiAgICAgICAgICAgICAgICAgICAgICAiYXJ0aXN0X2tleSIgPSJBcnRpc3QiLCANCiAgICAgICAgICAgICAgICAgICAgICAiZXh0ZXJuYWxfdXJsc19zcG90aWZ5IiA9ICJVUkwiKSkNCg0KDQpqb2luX2hpc3RvZ3JhbSRpc2luY2hhcnRbaXMubmEoam9pbl9oaXN0b2dyYW0kaXNpbmNoYXJ0KV0gPC0gMA0KDQpqb2luX2hpc3RvZ3JhbSRpc2luY2hhcnQgPC0gZmFjdG9yKGpvaW5faGlzdG9ncmFtJGlzaW5jaGFydCkNCg0KDQpoMTEgPC0gaGlzdChqb2luX2hpc3RvZ3JhbVtqb2luX2hpc3RvZ3JhbSRpc2luY2hhcnQ9PTEsJ2luc3RydW1lbnRhbG5lc3MnXSkNCmgxMSRkZW5zaXR5IDwtICBoMTEkY291bnRzL3N1bShoMTEkY291bnRzKSoxMDANCg0KaDEyIDwtIGhpc3Qoam9pbl9oaXN0b2dyYW1bam9pbl9oaXN0b2dyYW0kaXNpbmNoYXJ0PT0wLCdpbnN0cnVtZW50YWxuZXNzJ10pDQpoMTIkZGVuc2l0eSA8LSAgaDEyJGNvdW50cy9zdW0oaDEyJGNvdW50cykqMTAwDQoNCmgyMSA8LSBoaXN0KGpvaW5faGlzdG9ncmFtW2pvaW5faGlzdG9ncmFtJGlzaW5jaGFydD09MSwnaW5zdHJ1bWVudGFsbmVzc19sb2dhZGp1c3QnXSkNCmgyMSRkZW5zaXR5IDwtICBoMjEkY291bnRzL3N1bShoMjEkY291bnRzKSoxMDANCg0KaDIyIDwtIGhpc3Qoam9pbl9oaXN0b2dyYW1bam9pbl9oaXN0b2dyYW0kaXNpbmNoYXJ0PT0wLCdpbnN0cnVtZW50YWxuZXNzX2xvZ2FkanVzdCddKQ0KaDIyJGRlbnNpdHkgPC0gIGgyMiRjb3VudHMvc3VtKGgyMiRjb3VudHMpKjEwMA0KDQojcG5nKCJDOi9Vc2Vycy9Bc3VzL0Rlc2t0b3AvREFUQSBTQ0lFTkNFL01BRVNUUklBL0RhdGEgTWluaW5nL1RQL2dyYWZpY29zL2luc3RydW1lbnRhbG5lc3MucG5nIiwNCiMgICAgd2lkdGggPSA4MDAsIGhlaWdodCA9IDgwMCkNCnBhcihtZnJvdyA9IGMoMywyKSkNCnBsb3QoaDExLCBtYWluPSdpbnN0cnVtZW50YWxuZXNzIFxuY2hhcnQnLCB4bGFiPSIiLCB5bGFiPSJQb3JjZW50YWdlIiwgZnJlcT1GQUxTRSwgY29sPSdncmV5JywgeWxpbSA9IGMoMCwxMDApKQ0KcGxvdChoMTIsIG1haW49J2luc3RydW1lbnRhbG5lc3MgXG5mdWVyYSBjaGFydCcsIHhsYWI9IiIsIHlsYWI9IlBvcmNlbnRhZ2UiLCBmcmVxPUZBTFNFLCBjb2w9J2dyZXknLCB5bGltID0gYygwLDEwMCkpDQpwbG90KGgyMSwgbWFpbiA9Imluc3RydW1lbnRhbG5lc3NfbG9nIFxuY2hhcnQiLCB4bGFiPSIiLCB5bGFiPSJQb3JjZW50YWdlIiwgZnJlcT1GQUxTRSwgY29sPSdncmV5JywgeWxpbSA9IGMoMCwxMDApKQ0KcGxvdChoMjIsIG1haW4gPSJpbnN0cnVtZW50YWxuZXNzX2xvZyBcbmZ1ZXJhIGNoYXJ0IiwgeGxhYj0iIiwgeWxhYj0iUG9yY2VudGFnZSIsIGZyZXE9RkFMU0UsIGNvbD0nZ3JleScsIHlsaW0gPSBjKDAsMTAwKSkNCmJveHBsb3Qoam9pbl9oaXN0b2dyYW1bam9pbl9oaXN0b2dyYW0kaXNpbmNoYXJ0PT0xLCdpbnN0cnVtZW50YWxuZXNzX2xvZ2FkanVzdCddLCBtYWluPSJpbnN0cnVtZW50YWxuZXNzX2xvZyBjaGFydCIsIGhvcml6b250YWwgPSBUKQ0KYm94cGxvdChqb2luX2hpc3RvZ3JhbVtqb2luX2hpc3RvZ3JhbSRpc2luY2hhcnQ9PTAsJ2luc3RydW1lbnRhbG5lc3NfbG9nYWRqdXN0J10sIG1haW49Imluc3RydW1lbnRhbG5lc3NfbG9nIGZ1ZXJhIGNoYXJ0IiwgaG9yaXpvbnRhbCA9IFQpDQojZGV2Lm9mZigpDQoNCmBgYA0KDQoNCg0KIyMjIFotU2NvcmUgZGUgVmFyaWFibGVzIHF1ZSAidGllbmRlbiBhIGxhIG5vcm1hbCINCmBgYHtyfQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojIyBGSUxUUkFNT1MgT1VUTElFUlMgUE9SIFotU0NPUkUgcGFyYSAnZGFuY2VhYmlsaXR5JywgJ3RlbXBvJywgJ3ZhbGVuY2UnDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojei1zY29yZSBwYXJhIHZhcmlhYmxlcyBxdWUgdGllbmRlbiBhIGxhIG5vcm1hbA0KI2ZpbHRybyBmZWF0dXJlcyBudW1lcmljb3MgDQoNCiNkaXZpZG8gbG9zIGZlYXR1cmVzIHBvciBzdSBkaXN0cmlidWNpw7NuDQpmZWF0dXJlc19jb250aW51YXNfbWVkaWEgPC0gYygnZGFuY2VhYmlsaXR5JywgJ3RlbXBvJywgJ3ZhbGVuY2UnKQ0KZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhIDwtIGRmX2F1ZGlvX2ZlYXR1cmVzWyxmZWF0dXJlc19jb250aW51YXNfbWVkaWFdDQoNCiNub3JtYWxpem8geiBzY29yZSBjb24gbGFzIHZhcmlhYmxlcyBxdWUgdGllbmRlbiBhIGxhIG5vcm1hbA0KDQp6c2NvcmVfY29scyA8LSBjKCkNCmZvcihjb2wgaW4gbmFtZXMoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhKSl7DQogIG5hbWVfY29sIDwtIHBhc3RlKCd6c2NvcmVfJyxjb2wsIHNlcCA9ICIiKQ0KICB6c2NvcmVfY29scyA8LSBhcHBlbmQoenNjb3JlX2NvbHMsIG5hbWVfY29sKQ0KICBtZWRpYSA8LSAgbWVhbihkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFbLGNvbF0pDQogIHN0ZHYgPC0gc2QoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhWyxjb2xdKQ0KICBkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFbLG5hbWVfY29sXSA8LSAoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhWyxjb2xdIC0gbWVkaWEpL3N0ZHYNCiAgfQ0KDQpwYXIobWZyb3c9YygxLGxlbmd0aCh6c2NvcmVfY29scykpKQ0KbGFwcGx5KHpzY29yZV9jb2xzLCBmdW5jdGlvbihjb2wpIGJveHBsb3QoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhWyxjb2xdLHhsYWI9Y29sKSkNCmBgYA0KDQojIyMgQW5hbGlzaXMgZGUgWi1TY29yZSBwb3IgdmFyaWFibGUNCiANCkRhbmNlYWJpbGl0eQ0KDQpgYGB7cn0NCiN2YXJpYWJsZTogZGFuY2VhYmlsaXR5DQoNCnVtYnJhbF96c2NvcmUgPC0gMw0KY29uZGl0aW9ucyA8LSAoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhJHpzY29yZV9kYW5jZWFiaWxpdHk+IHVtYnJhbF96c2NvcmUpIHwgKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYSR6c2NvcmVfZGFuY2VhYmlsaXR5PCAtMSp1bWJyYWxfenNjb3JlKQ0KZGZfYXVkaW9fZmVhdHVyZXNbY29uZGl0aW9ucyxdICU+JQ0KICBzZWxlY3QoYWxidW1fbmFtZSxhcnRpc3RfbmFtZSwgZGFuY2VhYmlsaXR5ICkgJT4lDQogIGFycmFuZ2UoLWRhbmNlYWJpbGl0eSkNCmBgYA0KDQpUZW1wbw0KDQpgYGB7cn0NCiN2YXJpYWJsZTogVGVtcG8NCg0KdW1icmFsX3pzY29yZSA8LSAzDQpjb25kaXRpb25zIDwtIChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWEkenNjb3JlX3RlbXBvPiB1bWJyYWxfenNjb3JlKSB8IChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWEkenNjb3JlX3RlbXBvPCAtMSp1bWJyYWxfenNjb3JlKQ0KZGZfYXVkaW9fZmVhdHVyZXNbY29uZGl0aW9ucyxdICU+JQ0KICBzZWxlY3QoYWxidW1fbmFtZSxhcnRpc3RfbmFtZSwgdGVtcG8gKSAlPiUNCiAgYXJyYW5nZSgtdGVtcG8pDQpgYGANCg0KVmFsZW5jZQ0KDQpgYGB7cn0NCiN2YXJpYWJsZTogdmFsZW5jZQ0KdW1icmFsX3pzY29yZSA8LSAzDQpjb25kaXRpb25zIDwtIChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWEkenNjb3JlX3ZhbGVuY2U+IHVtYnJhbF96c2NvcmUpIHwgKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYSR6c2NvcmVfdmFsZW5jZTwgLTEqdW1icmFsX3pzY29yZSkNCmRmX2F1ZGlvX2ZlYXR1cmVzW2NvbmRpdGlvbnMsXSAlPiUNCiAgc2VsZWN0KGFsYnVtX25hbWUsYXJ0aXN0X25hbWUsIHZhbGVuY2UgKSAlPiUNCiAgYXJyYW5nZSgtdmFsZW5jZSkNCmBgYA0KDQojIyMgWi1TY29yZSBNb2RpZmljYWRvIGRlIFZhcmlhYmxlcyBBc2ltZXRyaWNhcw0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMjIEZJTFRSQU1PUyBPVVRMSUVSUyBQT1IgWi1TQ09SRSBNT0RJRklDQURPIHBhcmEgJ2Fjb3VzdGljbmVzcycsICdkdXJhdGlvbl9tcycsICdlbmVyZ3knLCAgJ2luc3RydW1lbnRhbG5lc3MnLCAnbGl2ZW5lc3MnLCAnbG91ZG5lc3MnLCAnc3BlZWNoaW5lc3MnLCAnY2FudF9tYXJrZXRzJw0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KZmVhdHVyZXNfY29udGludWFzX21lZGlhbmEgPC0gYygnYWNvdXN0aWNuZXNzJywgJ2R1cmF0aW9uX21zJywgJ2VuZXJneScsICdpbnN0cnVtZW50YWxuZXNzJywgJ2xpdmVuZXNzJywgJ2xvdWRuZXNzJywgJ3NwZWVjaGluZXNzJywgJ2NhbnRfbWFya2V0cycpDQoNCmRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYW5hIDwtIGRmX2F1ZGlvX2ZlYXR1cmVzWyxmZWF0dXJlc19jb250aW51YXNfbWVkaWFuYV0NCg0KDQoNCnpzY29yZW1vZGlmX2NvbHMgPC0gYygpDQpmb3IoY29sIGluIG5hbWVzKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYW5hKSl7DQogIG5hbWVfY29sIDwtIHBhc3RlKCd6c2NvcmVtb2RpZl8nLGNvbCwgc2VwID0gIiIpDQogIHpzY29yZW1vZGlmX2NvbHMgPC0gYXBwZW5kKHpzY29yZW1vZGlmX2NvbHMsIG5hbWVfY29sKQ0KICBtZWQgPSBtZWRpYW4oZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhbmFbLGNvbF0sIG5hLnJtID0gVCkNCiAgTUFEID0gbWVkaWFuKGFicyhkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFuYVssY29sXSAtIG1lZCksIG5hLnJtID0gVCkNCiAgZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhbmFbLCBuYW1lX2NvbF0gPC0gMC42NzQ1ICogKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYW5hWyxjb2xdIC0gbWVkKSAvIE1BRA0KfQ0KDQoNCnBhcihtZnJvdz1jKDQsMikpDQpsYXBwbHkoenNjb3JlbW9kaWZfY29scywgZnVuY3Rpb24oY29sKSBib3hwbG90KGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYW5hWyxjb2xdLHhsYWI9Y29sLCBob3Jpem9udGFsID0gVCkpDQoNCmBgYA0KDQoNClJldmlzacOzbiBWYXJpYWJsZSBgSW5zdHJ1bWVudGFsbmVzc2ANCmBgYHtyfQ0KaW5zdHJ1bWVudGFsbmVzcyA8LSBjKCJpbnN0cnVtZW50YWxuZXNzIiwgInpzY29yZW1vZGlmX2luc3RydW1lbnRhbG5lc3MiKSANCg0KeCA8LSBkZl9hdWRpb19mZWF0dXJlcyRpbnN0cnVtZW50YWxuZXNzDQoNCm5faW50ZXJ2IDwtIDEwDQoNCg0KaW50ZXJ2YWxvcyA8LSByb3VuZChzZXEoMCxtYXgoeCksYnk9KG1heCh4KS1taW4oeCkpL25faW50ZXJ2KSwyKQ0KDQpsYWJzIDwtIGMoKQ0KZm9yIChpIGluIDE6bl9pbnRlcnYpew0KbGFiIDwtIHBhc3RlKGludGVydmFsb3NbaV0saW50ZXJ2YWxvc1tpKzFdLCBzZXA9J1xuJykNCmxhYnMgPC0gYXBwZW5kKGxhYnMsIGxhYikNCiAgICANCn0NCg0KYmlucyA8LSBjdXQoeCwgbl9pbnRlcnYsIGluY2x1ZGUubG93ZXN0ID0gVFJVRSwgbGFiZWxzID0gbGFicykNCg0KYmFycGxvdCh0YWJsZShiaW5zKSkNCg0KYGBgDQoNCkhhY2Vtb3MgSy1tZWFucyBwYXJhIHBvZGVyIGRpc2NyZXRpemFyIGxhIHZhcmlhYmxlLiANCg0KYGBge3J9DQpzc2UgPC0gYygpDQpmb3IgKGsgaW4gMjo2KXsNCiAgY2x1c3RlcnMgPC0ga21lYW5zKGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3MsY2VudGVycyA9IGssIGl0ZXIubWF4ID0gMTAsIG5zdGFydCA9IGspDQogIHNzZSA8LSBhcHBlbmQoc3NlLCBjbHVzdGVycyR0b3Qud2l0aGluc3MpDQp9DQoNCnBsb3QoMjo2LHNzZSwgdHlwZSA9ICdsJywgeGxhYj0nQ2FudGlkYWQgZGUgQ2x1c3RlcnMnLCB5bGFiPSdTdW1hIEVycm9yIEN1YWRyw6F0aWNvJykNCg0KI2s9MyANCmNsdXN0ZXJzMyA8LSBrbWVhbnMoZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzcyxjZW50ZXJzID0gMywgaXRlci5tYXggPSAxMCwgbnN0YXJ0ID0gMykNCg0KZGZfYXVkaW9fZmVhdHVyZXMkY2x1c3RlcnMgPC0gZmFjdG9yKGNsdXN0ZXJzMyRjbHVzdGVyKQ0KDQpsZXYgPC0gbGV2ZWxzKGRmX2F1ZGlvX2ZlYXR1cmVzJGNsdXN0ZXJzKQ0KDQpsYWJzIDwtIGMoKQ0KZm9yIChpIGluIGxldil7DQogIG1pbiA8LSBtaW4oZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzc1tkZl9hdWRpb19mZWF0dXJlcyRjbHVzdGVycz09aV0pDQogIG1heCA8LSBtYXgoZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzc1tkZl9hdWRpb19mZWF0dXJlcyRjbHVzdGVycz09aV0pDQogIGxhYiA8LSBwYXN0ZShtaW4sbWF4LCBzZXA9JyAtICcpDQogIGxhYnMgPC0gYXBwZW5kKGxhYnMsIGxhYikNCn0NCg0KbGFicw0KDQojIGJhcnBsb3QodGFibGUoZmFjdG9yKGNsdXN0ZXJzMyRjbHVzdGVyKSksIGxhYmVscyA9IGxhYnMpDQoNCg0KDQpgYGANCg0KDQojcHJ1ZWJhIGlnYWwgZGUgdHJhbnNmb3JtYWNpb24geSB0ZXN0IGRlIG5vcm1hbGlkYWQNCmBgYHtyfQ0Kam9pbl9hdWRpb19jaGFydHNbMTo1LCJhY291c3RpY25lc3MiXV4yDQoNCmxpYnJhcnkobm9ydGVzdCkNCg0KbG9nMTAoZGZfY2hhcnRfd19seXJpY3MkYWNvdXN0aWNuZXNzKQ0KDQpmb3IgKGkgaW4gZmVhdHVyZXNfY29udGludWFzKXsNCiAgIHggPC0gbG9nMTAoZGZfY2hhcnRfd19seXJpY3NbLGldKQ0KICAgeCA8LSBzaGFwaXJvLnRlc3QoeCkNCiAgIHogPC0geCRwLnZhbHVlDQogIHByaW50KHopDQogIH0NCg0KDQpgYGANCg0KDQoNCiMgTFlSSUNTDQoNCg0KIyMgRmlsdHJvIHBvciBpZGlvbWENCmBgYHtyfQ0KIyBseXJpY3MgPSBtb25nbyhjb2xsZWN0aW9uID0gImx5cmljcyIsIGRiID0gInNwb3RpZnlfZG0iICkNCiMgZGZfbHlyaWNzIDwtIGx5cmljcyRmaW5kKCd7fScpDQojIA0KIyB3cml0ZS5jc3YoZGZfbHlyaWNzLCAiZGF0YS9kZl9seXJpY3MuY3N2IikNCg0KZGZfbHlyaWNzIDwtIHJlYWQuY3N2KCJkYXRhL2RmX2x5cmljcy5jc3YiKSAlPiUgDQogIHNlbGVjdCgtWCkNCg0KZGZfbHlyaWNzX3VuaWNhcyA8LSBkZl9seXJpY3MgJT4lIA0KICBkaXN0aW5jdChhcnRpc3RfbmFtZSwgdHJhY2tfbmFtZSwgbHlyaWNzKQ0KDQoNCiNmaWx0cm8gZGUgaWRpb21hDQpzcGFfbHlyaWNzID0gZGZfbHlyaWNzX3VuaWNhc1t0ZXh0Y2F0KGRmX2x5cmljc191bmljYXMkbHlyaWNzKT09InNwYW5pc2giLF0NCnNwYV9seXJpY3MNCg0KZW5fbHlyaWNzID0gZGZfbHlyaWNzX3VuaWNhc1t0ZXh0Y2F0KGRmX2x5cmljc191bmljYXMkbHlyaWNzKSAlaW4lIGMoImVuZ2xpc2giLCAic2NvdHMiKSxdDQplbl9seXJpY3MNCg0KI2NoZXF1ZW8gY2FudGlkYWQgZGUgY2FuY2lvbmVzIHBvciBpZGlvbWENCjEwMCoobnJvdyhlbl9seXJpY3MpICsgbnJvdyhzcGFfbHlyaWNzKSkvbnJvdyhkZl9seXJpY3NfdW5pY2FzKQ0KDQojIHRhYmxhIGNvbnRpbmdlbmNpYSBkZSBpZGlvbWFzDQppZGlvbWFzID0gdGV4dGNhdChkZl9seXJpY3NfdW5pY2FzJGx5cmljcykNCiMgc29ydCh0YWJsZShpZGlvbWFzKSwgZGVjcmVhc2luZyA9IFQpDQoNCmBgYA0KDQoNCiMjIyBsaW1waWV6YSBlc3Bhw7FvbA0KYGBge3J9DQojIGNvbWVudGFyIHkgZGVzY29tZW50YXIgc2Vnw7puIHNlIGVsaWphIHVuIGRhdGFmcmFtZSB1IG90cm8NCiMgZGZfbHlyaWNzX3NlbGVjY2lvbmFkbyA9IGRmX2x5cmljc191bmljYXMNCmRmX2x5cmljc19zZWxlY2Npb25hZG8gPSBlbl9seXJpY3MNCg0KY29ycHVzID0gQ29ycHVzKFZlY3RvclNvdXJjZShlbmMydXRmOChkZl9seXJpY3Nfc2VsZWNjaW9uYWRvJGx5cmljcykpKQ0KDQojIEVsaW1pbmFtb3MgZXNwYWNpb3MNCmNvcnB1cy5wcm8gPC0gdG1fbWFwKGNvcnB1cywgc3RyaXBXaGl0ZXNwYWNlKQ0KaW5zcGVjdChjb3JwdXMucHJvWzFdKQ0KDQojIEVsaW1pbm8gdG9kbyBsbyBxdWUgYXBhcmVjZSBhbnRlcyBkZWwgcHJpbWVyIFtdDQpjb3JwdXMucHJvIDwtIHRtX21hcChjb3JwdXMucHJvLCBjb250ZW50X3RyYW5zZm9ybWVyKA0KICBmdW5jdGlvbih4KSBzdWIoJ14uKz9cXFsuKj9cXF0nLCIiLCB4KSkpDQojIGluc3BlY3QoY29ycHVzLnByb1sxXSkNCg0KIyBFbGltaW5vIGxhcyBhY2xhcmFjaW9uZXMgZW4gbGFzIGNhbmNpb25lcywgcG9yIGVqZW1wbG86DQojIFtWZXJzbyAxOiBMdWlzIEZvbnNpICYgRGFkZHkgWWFua2VlXQ0KY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgY29udGVudF90cmFuc2Zvcm1lcigNCiAgZnVuY3Rpb24oeCkgZ3N1YignXFxbLio/XFxdJywgJycsIHgpKSkNCg0KIyBFbGltaW5vIHRvZG8gbG8gcXVlIGFwYXJlY2UgbHVlZ28gZGUgJ01vcmUgb24gR2VuaXVzJw0KY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbih4KSBnc3ViKCJNb3JlIG9uIEdlbml1cy4qIiwiIiwgeCkpKQ0KDQojIENvbnZlcnRpbW9zIGVsIHRleHRvIGEgbWluw7pzY3VsYXMNCmNvcnB1cy5wcm8gPC0gdG1fbWFwKGNvcnB1cy5wcm8sIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQoNCiMgcmVtb3ZlbW9zIG7Dum1lcm9zDQpjb3JwdXMucHJvIDwtIHRtX21hcChjb3JwdXMucHJvLCByZW1vdmVOdW1iZXJzKQ0KDQojIFBvZGVtb3MgYWdyZWdhciBwYWxhYnJhcyBhIGxhcyBzdG9wd29yZHMNCiMgbXlfc3RvcHdvcmRzIDwtIGFwcGVuZChzdG9wd29yZHMoInNwYW5pc2giKSwgJ3BhbGFicmEnKQ0KbXlfc3RvcHdvcmRzIDwtIGFwcGVuZChzdG9wd29yZHMoImVuZ2xpc2giKSwgYygneWVhaCcsICJhaW50IiwgImdldCIsICJnb3QiKSkNCg0KIyBSZW1vdmVtb3MgcGFsYWJyYXMgdmFjaWFzIA0KY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQ0KY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgcmVtb3ZlV29yZHMsIG15X3N0b3B3b3JkcykNCiMgY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgcmVtb3ZlV29yZHMsIHN0b3B3b3Jkcygic3BhbmlzaCIpKQ0KIyBpbnNwZWN0KGNvcnB1cy5wcm9bMV0pDQoNCg0KIyBSZW1vdmVtb3MgcHVudHVhY2lvbmVzDQpjb3JwdXMucHJvIDwtIHRtX21hcChjb3JwdXMucHJvLCByZW1vdmVQdW5jdHVhdGlvbikNCg0KIyBSZW1vdmVtb3MgdG9kbyBsbyBxdWUgbm8gZXMgYWxmYW51bcOpcmljbw0KY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbih4KSBzdHJfcmVwbGFjZV9hbGwoeCwgIltbOnB1bmN0Ol1dIiwgIiAiKSkpDQoNCiMgRW4gdG1fbWFwIHBvZGVtb3MgdXRpbGl6YXIgZnVuY2lvbmVzIHByb3ANCmxpYnJhcnkoc3RyaW5naSkNCnJlcGxhY2VBY2VudG9zIDwtIGZ1bmN0aW9uKHgpIHtzdHJpX3RyYW5zX2dlbmVyYWwoeCwgIkxhdGluLUFTQ0lJIil9DQpjb3JwdXMucHJvIDwtIHRtX21hcChjb3JwdXMucHJvLCByZXBsYWNlQWNlbnRvcykNCg0KIyBFbGltaW5hbW9zIGVzcGFjaW9zIHF1ZSBzZSB2YW4gZ2VuZXJhbmRvIGNvbiBsb3MgcmVlbXBsYXpvcw0KY29ycHVzLnBybyA8LSB0bV9tYXAoY29ycHVzLnBybywgc3RyaXBXaGl0ZXNwYWNlKQ0KYGBgDQoNCiMjIyBsaW1waWV6YSBpbmdsZXMNCmBgYHtyfQ0KI2Z1bmNpb25lcw0KI2Z1bmNpb24gcGFyYSBjb3JyZWdpciBwYWxhYnJhcw0KZGVjb250cmFjdGVkID0gZnVuY3Rpb24odHh0KXsNCiAgdHh0ID0gZ3N1Yigid29uJ3QiLCAid2lsbCBub3QiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcJ3MiLCAiIGlzIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXCd0IiwgIiBub3QiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcJ2xsIiwgIiB3aWxsIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXCdtIiwgIiBhbSIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwncmUiLCAiIGFyZSIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwnZCIsICIgaGFkIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXCd2ZSIsICIgaGF2ZSIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiY291bGRuIiwgImNvdWxkIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJkb24iLCAiZG8iLCB0eHQpDQogIHR4dCA9IGdzdWIoImRvZXNuIiwgImRvZXMiLCB0eHQpDQogIHR4dCA9IGdzdWIoImlzbiIsICJpcyIsIHR4dCkNCiAgdHh0ID0gZ3N1YigibXVzdG4iLCAibXVzdCIsIHR4dCkNCiAgdHh0ID0gZ3N1Yigic2hvdWxkbiIsICJzaG91bGQiLCB0eHQpDQogIHR4dCA9IGdzdWIoIndhc24iLCAid2FzIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXCdjYXVzZSIsICIgYmVjYXVzZSIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwnIiwgImciLCB0eHQpDQogIHJldHVybih0eHQpDQp9DQoNCg0KI0Z1bmNpw7NuIHBhcmEgbGltcGlhci4gDQp0ZXh0X2NsZWFuaW5nID0gZnVuY3Rpb24odHh0LCBzdG9wPUZBTFNFLCBsYW5ndWFnZSl7DQogIA0KICB0eHQgPSBzdWIoJ14uKz9cXFsuKj9cXF0nLCIiLCB0eHQpICNvaw0KICB0eHQgPSBzdWIoIk1vcmUgb24gR2VuaXVzLioiLCIiLCB0eHQpDQogIHR4dCA9IGdzdWIoJ1xcWy4qP1xcXScsICcnLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcbiIsIiAiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlsoKV0iLCAiICIsIHR4dCkNCiAgdHh0ID0gdG9sb3dlcih0eHQpDQogIHR4dCA9IGRlY29udHJhY3RlZCh0eHQpDQogIHR4dCA9IGdzdWIoIlxcVytcXGIiLCAiICIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFxkIiwgIiAiLCB0eHQpDQogIA0KICBzdG9wd29yZHNfcmVnZXggPSBwYXN0ZShzdG9wd29yZHMoJ2VuJyksIGNvbGxhcHNlID0gJ1xcYnxcXGInKQ0KICBzdG9wd29yZHNfcmVnZXggPSBwYXN0ZTAoJ1xcYicsIHN0b3B3b3Jkc19yZWdleCwgJ1xcYicpDQogIHR4dCA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbCh0eHQsIHN0b3B3b3Jkc19yZWdleCwgJycpDQoNCiAgbXlfc3RvcHdvcmRzIDwtIGMoJ29vaCcsICd5ZWFoJywgImFpbnQiLCAiZ2V0IiwgImdvdCIsICJheXkiKQ0KICB0eHQgPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwodHh0LCBteV9zdG9wd29yZHMsICcnKQ0KICAgDQogIHR4dCA9IHN0cl90cmltKHR4dCkNCiAgdHh0ID0gZ3N1YigiXFxuIiwiICIsIHR4dCkNCiAgDQogIGlmKGxhbmd1YWdlID09ICJlbiIpew0KICAgIHJldHVybih0eHQpDQogIH1lbHNlIGlmIChsYW5ndWFnZSA9PSAiZXMiKXsNCiAgICB0eHQgPC0gZnVuY3Rpb24oeCkge3N0cmlfdHJhbnNfZ2VuZXJhbCh4LCAiTGF0aW4tQVNDSUkiKX0NCiAgICAgIHJldHVybih0eHQpIA0KICB9ZWxzZXsNCiAgICAgICAgcmV0dXJuKCJGYWx0YSBkZWZpbmlyIGxlbmd1YWplIikNCiAgICAgIH0NCn0NCg0KI2Z1bmNpw7NuIHBhcmEgb2J0ZW5lciBvcmFjaW9uZXMgZGUgdW5hIHNvbGEgcGFsYWJyYS4gDQpvbmVfd29yZF9zZXRlbmNlcyA9IGZ1bmN0aW9uKHR4dCl7DQogIHJldHVybihnc3ViKCJcXFcrXFxiIiwgIi4gIiwgdHh0KSkNCn0NCg0KI2xpbXBpbyBsYXMgbGV0cmFzIGVuIGluZ2xlcw0KZW5fbHlyaWNzJGx5cmljcyA9IHRleHRfY2xlYW5pbmcoZW5fbHlyaWNzJGx5cmljcywgbGFuZ3VhZ2UgPSAiZW4iKQ0KDQpoZWFkKGVuX2x5cmljcyRseXJpY3MsIDEpDQoNCg0KYGBgDQoNCg0KDQojIyBFeHBsaWNpdA0KIyMjIEVzcGHDsW9sDQpgYGB7cn0NCiNEaWNjaW9uYXJpbyBlc3Bhw7FvbA0KbWFsYXNfcGFsYWJyYXNfMSA8LSByZWFkX2NzdigiZGF0YS9tYWxhc19wYWxhYnJhcy50eHQiLCANCiAgICBjb2xfbmFtZXMgPSBGQUxTRSkNCg0KbWFsYXNfcGFsYWJyYXNfMiA8LSByZWFkX2NzdigiZGF0YS9tYWxhc19wYWxhYnJhc190cmFuc2xhdGUudHh0IiwgDQogICAgY29sX25hbWVzID0gRkFMU0UpDQoNCm1hbGFzX3BhbGFicmFzXzMgPC0gcmVhZF9jc3YoImRhdGEvbWFsYXNfcGFsYWJyYXNfd2lraS50eHQiLCANCiAgICBjb2xfbmFtZXMgPSBGQUxTRSkgJT4lIA0KICBzZWxlY3QoWDEpDQoNCm1hbGFzX3BhbGFicmFzXzQgPC0gcmVhZF9jc3YoImRhdGEvcGFsYWJyYXNfcHJvZmFuYXNfZXMudHh0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF9uYW1lcyA9IEZBTFNFKQ0KDQptYWxhc19wYWxhYnJhcyA8LSByYmluZChtYWxhc19wYWxhYnJhc18xLCBtYWxhc19wYWxhYnJhc18yLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWFsYXNfcGFsYWJyYXNfMywgbWFsYXNfcGFsYWJyYXNfNCkNCg0KDQojRnVuY2nDs24gcGFyYSBsaW1waWFyLiANCnRleHRfY2xlYW5pbmdfZXNwID0gZnVuY3Rpb24odHh0LCBzdG9wPUZBTFNFKXsNCiAgdHh0ID0gc3ViKCdeLis/XFxbLio/XFxdJywiIiwgdHh0KSAjb2sNCiAgdHh0ID0gc3ViKCJNb3JlIG9uIEdlbml1cy4qIiwiIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCdcXFsuKj9cXF0nLCAnJywgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXG4iLCIgIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJbKCldIiwgIiAiLCB0eHQpDQogIHR4dCA9IHRvbG93ZXIodHh0KQ0KICAjIHR4dCA9IGRlY29udHJhY3RlZCh0eHQpDQogIHR4dCA9IGdzdWIoIlxcVytcXGIiLCAiICIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFxkIiwgIiAiLCB0eHQpDQogIHR4dCA9IHN0cl90cmltKHR4dCkNCiAgIyB0eHQgPSBzdHJpX3RyYW5zX2dlbmVyYWwodHh0LCAiTGF0aW4tQVNDSUkiKQ0KICByZXR1cm4odHh0KQ0KfQ0KDQoNCm1hbGFzX3BhbGFicmFzJGxpbXBpYXMgPSB0ZXh0X2NsZWFuaW5nKG1hbGFzX3BhbGFicmFzJFgxKQ0KbWFsYXNfcGFsYWJyYXMNCg0KbWFsYXNfcGFsYWJyYXMgJT4lIGZpbHRlcihzdGFydHNXaXRoKGxpbXBpYXMsICJnIikpDQoNCmBgYA0KDQojIyMgSW5nbMOpcw0KYGBge3J9DQojR2VuZXJvIGxpc3RhIGRlIG1hbGFzIHBhbGFicmFzDQpiYWRfd29yZHMgPC0gYygpDQpiYWRfd29yZHMgPC0gYXBwZW5kKGJhZF93b3JkcywgdW5pcXVlKHRvbG93ZXIobGV4aWNvbjo6cHJvZmFuaXR5X3phY19hbmdlcikpKQ0KYmFkX3dvcmRzIDwtIGFwcGVuZChiYWRfd29yZHMsIHVuaXF1ZSh0b2xvd2VyKGxleGljb246OnByb2Zhbml0eV9hbHZhcmV6KSkpDQpiYWRfd29yZHMgPC0gYXBwZW5kKGJhZF93b3JkcywgdW5pcXVlKHRvbG93ZXIobGV4aWNvbjo6cHJvZmFuaXR5X2Fycl9iYWQpKSkNCmJhZF93b3JkcyA8LSBhcHBlbmQoYmFkX3dvcmRzLCB1bmlxdWUodG9sb3dlcihsZXhpY29uOjpwcm9mYW5pdHlfcmFjaXN0KSkpDQpiYWRfd29yZHMgPC0gYXBwZW5kKGJhZF93b3JkcywgdW5pcXVlKHRvbG93ZXIobGV4aWNvbjo6cHJvZmFuaXR5X2Jhbm5lZCkpKQ0KYmFkX3dvcmRzIDwtIHVuaXF1ZShiYWRfd29yZHMpDQoNCmJpZ2xvdSA8LSByZWFkLmNzdigiaHR0cHM6Ly93d3cuY3MuY211LmVkdS9+YmlnbG91L3Jlc291cmNlcy9iYWQtd29yZHMudHh0IiwgaGVhZGVyPUZBTFNFLCBjb2wubmFtZXMgPSBjKCJ3b3JkcyIpKQ0KDQoNCiNGdW5jacOzbiBwYXJhIG9idGVuZXIgcGFsYWJyYXMgcHJvZmFuYXMgZGUgY2FkYSBseXJpYw0KZ2V0X3Byb2Zhbml0aWVzID0gZnVuY3Rpb24odHh0LCBwcm9mYW5pdHlfbHN0KXsNCiAgIyB0eHQgPSB0ZXh0X2NsZWFuaW5nKHR4dCkNCiAgd29yZHMgPSBhcy5kYXRhLmZyYW1lKHN0cnNwbGl0KHR4dCwgIlsgXSsiKSwgY29sLm5hbWVzID0gIndvcmRzIikNCiAgcHJvZmFuX2RmID0gcHJvZmFuaXR5KGdldF9zZW50ZW5jZXMod29yZHMpLCBwcm9mYW5pdHlfbGlzdCA9IHByb2Zhbml0eV9sc3QpDQogIHByb2Zhbl93b3JkcyA9IHByb2Zhbl9kZltwcm9mYW5fZGYkcHJvZmFuaXR5X2NvdW50IT0wLF0kd29yZHMNCiAgdmVjdG9yID0gYXMudmVjdG9yKHByb2Zhbl93b3JkcykNCiAgaWYgKGxlbmd0aCh2ZWN0b3IpPT0wKXsNCiAgICByZXR1cm4oTlVMTCkNCiAgfQ0KICBlbHNle3JldHVybihhcy52ZWN0b3IocHJvZmFuX3dvcmRzKSkNCiAgICB9DQp9DQoNCg0KDQplbl9seXJpY3MkcHJvZmFiZV9iaWdsb3UgPC0gbGFwcGx5KGVuX2x5cmljcyRseXJpY3MsICBmdW5jdGlvbih4KSBnZXRfcHJvZmFuaXRpZXMoeCwgYmlnbG91JHdvcmRzKSkNCg0KZW5fbHlyaWNzICU+JSANCiAgbXV0YXRlKHByb2ZhbmVfYmlnbG91ID0gdW5saXN0KGdldF9wcm9mYW5pdGllcyhseXJpY3MsIGJpZ2xvdSR3b3JkcykpKQ0KDQplbl9seXJpY3MkcHJvZmFiZV9iaWdsb3UgPSB1bmxpc3Qoc3Ryc3BsaXQoZW5fbHlyaWNzJHByb2ZhYmVfYmlnbG91LCBzcGxpdCA9ICIgIikpDQoNCmVuX2x5cmljcyRwcm9mYWJlX2JhZHdvcmRzIDwtIGxhcHBseShlbl9seXJpY3MkbHlyaWNzLCBmdW5jdGlvbih4KSBnZXRfcHJvZmFuaXRpZXMoeCwgYmFkX3dvcmRzKSkNCg0Kc3RyKGVuX2x5cmljcykNCmhlYWQoZW5fbHlyaWNzLDEpDQoNCmVuX2x5cmljcyRwcm9mYWJlX2JpZ2xvdVszXQ0KDQpgYGANCg0KDQojIyBNQVRSSVogVEVSTUlOTyBET0NVTUVOVE8NCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMjIyMjIyMgR2VuZXJhY2nDs24gZGUgbGEgTWF0csOteiBUw6lybWluby1Eb2N1bWVudG8gZGVsIGNvcnB1cyAjIyMjIyMjDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KY29ycHVzLnBybzJ0ZG0gPC0gZnVuY3Rpb24oY29ycHVzLCBwb25kZXJhY2lvbiwgbl90ZXJtcyl7DQogICNjb3JwdXMNCiAgDQogIA0KICAjbWF0cml6IFREIA0KICBkdG0gPC0gVGVybURvY3VtZW50TWF0cml4KGNvcnB1cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gbGlzdCh3ZWlnaHRpbmcgPSBwb25kZXJhY2lvbikpDQogIG1hdHJpel90ZCA8LSBhcy5tYXRyaXgoZHRtKQ0KICANCiAgDQogICMgQ2FsY3VsYW1vcyBsYSBmcmVjdWVuY2lhIGRlIGNhZGEgdMOpcm1pbm8gZW4gZWwgY29ycHVzDQogIGZyZXFfdGVybSA8LSBoZWFkKHNvcnQocm93U3VtcyhtYXRyaXpfdGQpLGRlY3JlYXNpbmc9VFJVRSksIG5fdGVybXMpDQogIA0KICAjbWF0cml6IHRyYW5zcHVlc3RhIGRlIGxvcyBuX3Rlcm1zIG1hcyBmcmVjdWVudGVzDQogIG1hdHJpel9uZiA8LSB0KG1hdHJpel90ZFtzb3J0KG5hbWVzKGZyZXFfdGVybSkpLCBdKQ0KICANCiAgI3Bhc2FqZSBhIGJpbmFyaW8NCiAgbWF0cml6X25mW21hdHJpel9uZj4wXSA8LSAxDQogIA0KICByZXR1cm4obWF0cml6X25mKQ0KICANCiAgfQ0KICANCmNvcnB1c19lbmcgPSBDb3JwdXMoVmVjdG9yU291cmNlKGVuYzJ1dGY4KGVuX2x5cmljcyRseXJpY3MpKSkNCm1hdHJpeiA8LSBjb3JwdXMucHJvMnRkbShjb3JwdXMgPSBjb3JwdXNfZW5nLCBwb25kZXJhY2lvbj0gIndlaWdodFRmIixuX3Rlcm1zPSAxNTApDQoNCmRpbShtYXRyaXopDQoNCmRmX3RtIDwtIGFzLmRhdGEuZnJhbWUobWF0cml6KQ0KaGVhZChkZl90bSwyKQ0KDQojIyBKb2luIG1hdHJpeiBkZSBwYWxhYnJhcyBjb24gYXJ0aXN0YSB5IHRyYWNrDQpkZl9seV9mZWF0IDwtIGNiaW5kKGRmX2x5cmljc19zZWxlY2Npb25hZG9bLWMoMyldLCBkZl90bSkNCg0KbnJvdyhkZl90bSkNCm5yb3coZGZfbHlyaWNzX3NlbGVjY2lvbmFkbykNCm5yb3coZGZfbHlfZmVhdCkNCg0KZmlsdGVyIDwtICFuYW1lcyhkZl9seV9mZWF0KSAlaW4lIGMoImFydGlzdF9uYW1lIiwgInRyYWNrX25hbWUiICkNCg0KZGZfbHlfZmVhdF9vayA8LSBkZl9seV9mZWF0WywgZmlsdGVyXQ0KIyBkZl9seV9mZWF0X29rID0gZGZfbHlfZmVhdF9va1ssIC0od2hpY2goY29sU3VtcyhkZl9seV9mZWF0X29rKSA9PSAwKSldDQoNCiMgY29sU3VtcyhkZl9seV9mZWF0X29rKQ0KDQpoZWFkKGRmX2x5X2ZlYXRfb2ssIDMpDQpoZWFkKGRmX2x5X2ZlYXQsIDMpDQoNCg0KZGZfbHlfZmVhdCRpZCA9IDE6bnJvdyhkZl9seV9mZWF0KQ0KDQpkZl9tZWx0IDwtIHJlc2hhcGUyOjptZWx0KGRhdGEgPSBkZl9seV9mZWF0WywzOm5jb2woZGZfbHlfZmVhdCldLCBpZC52YXJzID0gYygiaWQiKSkgICU+JQ0KICBhcnJhbmdlKGlkKQ0KDQpkZl9tZWx0IDwtIGRmX21lbHRbZGZfbWVsdCR2YWx1ZSAhPSAwLF0NCg0KZGZfbWVsdF90eHQgPC0gZGZfbWVsdFtkZl9tZWx0JHZhbHVlID09IDEsXQ0KZGZfbWVsdF9jYXQgPC0gZGZfbWVsdFtkZl9tZWx0JHZhbHVlICE9IDEsXQ0KDQpoZWFkKGRmX21lbHRfdHh0ICkNCmRpbShkZl9tZWx0X3R4dCApDQoNCiNkZW5vbWlubyBhIGxvcyB0w6lybWlub3MgcHJvZmFub3MNCmRmX21lbHRfdHh0IDwtIGRmX21lbHRfdHh0ICU+JSANCiAgbXV0YXRlKHZhcmlhYmxlID0gY2FzZV93aGVuKGFzLmNoYXJhY3Rlcih2YXJpYWJsZSkgJWluJSBiaWdsb3Ukd29yZHMgfg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoIlBST0ZfIiwgYXMuY2hhcmFjdGVyKHZhcmlhYmxlKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUIH4gcGFzdGUwKCJURVJNXyIsIGFzLmNoYXJhY3Rlcih2YXJpYWJsZSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApICANCiAgICAgICAgICkNCg0KZGZfbWVsdF90eHQgJT4lIGZpbHRlcihzdGFydHNXaXRoKHZhcmlhYmxlLCAiUFJPRiIpKQ0KDQoNCiMgZGZfbWVsdF90eHRbZGZfbWVsdF90eHQkdmFyaWFibGUgJWluJSBiaWdsb3Ukd29yZHMsXQ0KDQoNCmRmX21lbHRfdHh0X3RvX3J1bHMgPC0gZGZfbWVsdF90eHRbLCAtYygzKV0NCm5hbWVzKGRmX21lbHRfdHh0X3RvX3J1bHMpIDwtIGMoImlkIiwgIml0ZW0iKQ0KDQp3cml0ZS50YWJsZShkZl9tZWx0X3R4dF90b19ydWxzLCBmaWxlPSJkYXRhL3RyYW5zYWNjaW9uc19seXJpY3NfZmVhdHVyZXMudHh0Iiwgcm93Lm5hbWVzID0gRikNCg0KIyBSZWdsYXMNCiMgY2hlcXVlYXIgbmFuJ3MNCmx5cmljc190cmFucyA8LSByZWFkLnRyYW5zYWN0aW9ucygiZGF0YS90cmFuc2FjY2lvbnNfbHlyaWNzX2ZlYXR1cmVzLnR4dCIsIGZvcm1hdCA9ICJzaW5nbGUiLCBjb2xzID0gYygxLDIpKQ0KDQphcnVsZXM6Omluc3BlY3QoaGVhZChseXJpY3NfdHJhbnMsIDMpKQ0KDQpzdW1tYXJ5KGx5cmljc190cmFucykNCnJlZ2xhcyA8LSBhcHJpb3JpKGx5cmljc190cmFucywgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0PTAuMSwNCiAgICAgICAgICAgICAgICAgICAgY29uZmlkZW5jZSA9IDAuNSwgdGFyZ2V0ICA9ICJydWxlcyIgICkpDQoNCnJlZ2xhc19zdWIgPC0gc3Vic2V0KHJlZ2xhcywgc3Vic2V0ID0gcmhzICVwaW4lICJQUk9GXyIpDQphcnVsZXM6Omluc3BlY3QoaGVhZChzb3J0KHJlZ2xhc19zdWIsIGJ5ID0gImxpZnQiLCBkZWNyZWFzaW5nID0gVCksNSkpDQoNCmBgYA0KDQoNCiMjIENhcmdhIGRlIElnYWwgKFVOSUZJQ0FSKQ0KYGBge3J9DQpkZl9seXJpY3NfdW5pY2FzIDwtIGRmX2x5cmljcyAlPiUgZGlzdGluY3QoYXJ0aXN0X25hbWUsIHRyYWNrX25hbWUsIGx5cmljcykNCm5yb3coZGZfbHlyaWNzX3VuaWNhcykNCg0KZGZfY2hhcnRfd19seXJpY3MgPC0gbWVyZ2Uoam9pbl9hdWRpb19jaGFydHMsIGRmX2x5cmljc191bmljYXMsIGJ5LnggPSBjKCJhcnRpc3RfbmFtZSIsInRyYWNrX25hbWUiKSwgYnkueT0gYygiYXJ0aXN0X25hbWUiLCJ0cmFja19uYW1lIiksIGFsbC54PVRSVUUsIGFsbC55ID0gRkFMU0UpDQoNCmRmX2NoYXJ0X3dfbHlyaWNzIDwtIGRmX2NoYXJ0X3dfbHlyaWNzWyFpcy5uYShkZl9jaGFydF93X2x5cmljcyRseXJpY3MpLF0NCg0KYGBgDQoNCg0KIyMgRXhwbGljaXQNCg0KIyMjIGNvbnRhciBtYWxhcyBwYWxhYnJhcyAoUGFydGUgZGUgSWdhbDogVU5JRklDQVIpDQpgYGB7cn0NCg0KYmFkX3dvcmRzIDwtIGMoKQ0KYmFkX3dvcmRzIDwtIGFwcGVuZChiYWRfd29yZHMsIHVuaXF1ZSh0b2xvd2VyKGxleGljb246OnByb2Zhbml0eV96YWNfYW5nZXIpKSkNCmJhZF93b3JkcyA8LSBhcHBlbmQoYmFkX3dvcmRzLCB1bmlxdWUodG9sb3dlcihsZXhpY29uOjpwcm9mYW5pdHlfYWx2YXJleikpKQ0KYmFkX3dvcmRzIDwtIGFwcGVuZChiYWRfd29yZHMsIHVuaXF1ZSh0b2xvd2VyKGxleGljb246OnByb2Zhbml0eV9hcnJfYmFkKSkpDQpiYWRfd29yZHMgPC0gYXBwZW5kKGJhZF93b3JkcywgdW5pcXVlKHRvbG93ZXIobGV4aWNvbjo6cHJvZmFuaXR5X3JhY2lzdCkpKQ0KYmFkX3dvcmRzIDwtIGFwcGVuZChiYWRfd29yZHMsIHVuaXF1ZSh0b2xvd2VyKGxleGljb246OnByb2Zhbml0eV9iYW5uZWQpKSkNCg0KYmFkX3dvcmRzIDwtIHVuaXF1ZShiYWRfd29yZHMpDQoNCg0KY29udGFyX2JhZF93b3JkcyA8LSBmdW5jdGlvbih4KXsNCiAgeCA8LSBwcm9mYW5pdHkoeCxwcm9mYW5pdHlfbGlzdCA9IGJhZF93b3JkcykNCiAgcSA8LSBzdW0oeCRwcm9mYW5pdHlfY291bnQpDQogIHJldHVybiAocSkNCiAgfQ0KZGZfY2hhcnRfd19seXJpY3MkY2FudF9iYWRfd29yZHMgPC0gc2FwcGx5KGRmX2NoYXJ0X3dfbHlyaWNzWywibHlyaWNzIl0sIGNvbnRhcl9iYWRfd29yZHMpDQoNCg0KZGZfY2hhcnRfd19seXJpY3Nfb25seV9leHBsaWNpdCA8LSBkZl9jaGFydF93X2x5cmljc1tkZl9jaGFydF93X2x5cmljcyRleHBsaWNpdD09VFJVRSAmIGRmX2NoYXJ0X3dfbHlyaWNzJGNhbnRfYmFkX3dvcmRzID4gMCwgXQ0KDQpoaXN0KGRmX2NoYXJ0X3dfbHlyaWNzX29ubHlfZXhwbGljaXQkY2FudF9iYWRfd29yZHMpDQoNCg0KI2NyZW8gdmFycyBjYXRlZ8OzcmljYXMNCmRmX2NoYXJ0X3dfbHlyaWNzX29ubHlfZXhwbGljaXQkbml2ZWxfcHV0ZWFkYSA8LSBjdXQoZGZfY2hhcnRfd19seXJpY3Nfb25seV9leHBsaWNpdCRjYW50X2JhZF93b3JkcywgYnJlYWtzID0gYygwLDEwLDIwLDUwLEluZiksIGxhYmVscz1jKCJiYWpvIiwicG9jbyIsImFsdG8iLCJtdXlfYWx0byIpKQ0KDQpkZl9jaGFydF93X2x5cmljc19vbmx5X2V4cGxpY2l0JG5pdmVsX3JhbmtpbmcgPC0gY3V0KGRmX2NoYXJ0X3dfbHlyaWNzX29ubHlfZXhwbGljaXQkcG9zaXRpb25fYXZnLCBicmVha3MgPSBjKDEsMTAwLEluZiksIGxhYmVscz1jKCIxYTEwMCIsIjEwMGEyMDAiKSkNCg0KZGZfY2hhcnRfd19seXJpY3Nfb25seV9leHBsaWNpdCRuaXZlbF9wb3B1bGFyaWRhZCA8LSBjdXQoc3FydChkZl9jaGFydF93X2x5cmljc19vbmx5X2V4cGxpY2l0JGNhbnRfYmFkX3dvcmRzKSwgYnJlYWtzID0gYygwLDEwLDIwLDUwLEluZiksIGxhYmVscz1jKCJiYWpvIiwicG9jbyIsImFsdG8iLCJtdXlfYWx0byIpKQ0KDQp0cmFuc2FjdGlvbnMgPC0gYXMoYXMuZGF0YS5mcmFtZShhcHBseShkZl9jaGFydF93X2x5cmljc19vbmx5X2V4cGxpY2l0LCAyLCBhcy5mYWN0b3IpKSwgInRyYW5zYWN0aW9ucyIpDQpydWxlcyA9IGFwcmlvcmkodHJhbnNhY3Rpb25zLCBwYXJhbWV0ZXI9bGlzdCh0YXJnZXQ9InJ1bGVzIiwgY29uZmlkZW5jZT0wLjI1LCBzdXBwb3J0PTAuMSkpDQpydWxlcy5zdWIgPC0gc3Vic2V0KHJ1bGVzLCBzdWJzZXQgPSBsaHMgJXBpbiUgIm5pdmVsX3B1dGVhZGEiICYgcmhzICVwaW4lICJuaXZlbF9yYW5raW5nIikNCmluc3BlY3QoaGVhZChzb3J0KHJ1bGVzLnN1YiwgYnkgPSAibGlmdCIsIGRlY3JlYXNpbmcgPSBUUlVFKSwxMCkpDQoNCiMgZGlzY3JldGl6YWNpb24gY29udGludWFzIHkgc2VsZWNjaW9uIGRlIHZhcmlhYmxlcw0KIyBpZGVudGlmaWNhciBwYWxhYnJhcyBleHBsw610DQoNCmBgYA0KDQoNCg0KYGBge3J9DQoNCmBgYA0KDQoNCg0KDQoNCiMgTElNUElPIEhBU1RBIEFDQQ0KDQojIFByZWd1bnRhcyBkZSBpbnZlc3RpZ2FjaW9uDQoNCiMjIFBhdHJvbiBDb211biBDYW5jaW9uZXMgZGVsIENoYXJ0DQrCv1F1w6kgY2FyYWN0ZXLDrXN0aWNhcyB0aWVuZW4gbGFzIGNhbmNpb25lcyBxdWUgZXN0w6FuIGVuIGVsIGNoYXJ0PyDCv0N1YWwgZXMgZWwgcGF0csOzbiBjb211biBxdWUgdGllbmVuIGxhcyBjYW5jaW9uZXMgbcOhcyBlc2N1Y2hhZGFzPyAodmVyIGRpc3BlcnNpb25lcywgbWVkaWEsIGdyYWZpY28gY29tcGFyYXRpdm8pDQpgYGB7cn0NCg0KDQojZnVuY2lvbiBwYXJhIGVzY2FsYXIgdmFyaWFibGUNCnNjYWxlX3ZibGUgPC0gZnVuY3Rpb24oeCl7DQogICh4IC0gbWVhbih4LCBuYS5ybSA9IFQpKS9zZCh4LCBuYS5ybSA9IFQpDQp9DQoNCmBgYA0KYGBge3J9DQojYW50aV9qb2luDQphbnRpX2pvaW5fYXVkaW9fY2hhcnRzIDwtIGRmX2F1ZGlvX2ZlYXR1cmVzICU+JSANCiAgc2VsZWN0KCJhcnRpc3RfbmFtZSIsImFydGlzdF9hbGwiLCAiYXJ0aXN0X2tleSIsDQogICAgICAgICAidHJhY2tfbmFtZSIsICJleHRlcm5hbF91cmxzX3Nwb3RpZnkiLCAiYWxidW1fbmFtZSIsICJhbGJ1bV9yZWxlYXNlX3llYXIiLA0KICAgICAgICAgYWxsX29mKGZlYXR1cmVzX2NvbnRpbnVhcyksIGFsbF9vZihmZWF0dXJlc19jYXRlZ29yaWNhcykpICU+JSANCiAgYW50aV9qb2luKCBkZl9jaGFydHMgJT4lDQogICAgICAgICAgICAgICBzZWxlY3QoICJUcmFja19OYW1lIiwgIkFydGlzdCIsICJVUkwiKSwNCiAgICAgICAgICAgICAgIGJ5ID0gYygiZXh0ZXJuYWxfdXJsc19zcG90aWZ5IiA9IlVSTCIsDQogICAgICAgICAgICAgICAgICAgICAgImFydGlzdF9rZXkiID0iQXJ0aXN0IiAgKSkNCiAgICAgICAgICAgICAgICMgYnkgPSBjKCJ0cmFja19uYW1lIiA9ICJUcmFja19OYW1lIikpDQoNCg0KYW50aV9qb2luX2F1ZGlvX2NoYXJ0c19jb21wbGV0ZSA8LSBuYS5vbWl0KGFudGlfam9pbl9hdWRpb19jaGFydHMpDQphbnRpX2pvaW5fYXVkaW9fY2hhcnRzX2NvbXBsZXRlX3NjYWxlIDwtIGFudGlfam9pbl9hdWRpb19jaGFydHNfY29tcGxldGUgJT4lIA0KICBkaXN0aW5jdCgpICU+JSANCiAgc2VsZWN0KGZlYXR1cmVzX2NvbnRpbnVhcykgICU+JSANCiAgbXV0YXRlX2FsbChzY2FsZV92YmxlKQ0KbnJvdyhhbnRpX2pvaW5fYXVkaW9fY2hhcnRzX2NvbXBsZXRlX3NjYWxlKQ0KDQpgYGANCg0KIyMgUXXDqSB0ZW1hcyBwZXJkdXJhbiBtdWNobyBlbiBlbCByYW5raW5nDQoNCiMjIyBBcnRpc3RhcyBxdWUgbWFzIGFwYXJlY2VuIGVuIGVsIGNoYXJ0DQpgYGB7cn0NCmpvaW5fYXVkaW9fY2hhcnRzICU+JSANCiAgZ3JvdXBfYnkoYXJ0aXN0X25hbWUpICU+JSANCiAgZHBseXI6OnN1bW1hcmlzZShuID0gbigpKSAlPiUgDQogIGFycmFuZ2UoLW4pDQpgYGANCg0KIyMjIFRyYWNrcyBxdWUgbWFzIGFwYXJlY2VuIGVuIGVsIGNoYXJ0DQpgYGB7cn0NCmpvaW5fYXVkaW9fY2hhcnRzICU+JSANCiAgZ3JvdXBfYnkodHJhY2tfbmFtZSwgYXJ0aXN0X25hbWUsZXh0ZXJuYWxfdXJsc19zcG90aWZ5KSAlPiUgDQogIGRwbHlyOjpzdW1tYXJpc2UobiA9IG4oKSkgJT4lIA0KICBhcnJhbmdlKC1uKSAlPiUgDQogIHNlbGVjdCh0cmFja19uYW1lLCBuLCBldmVyeXRoaW5nKC4pKQ0KDQpgYGANCg0KDQojIMK/Q3XDoW50byB0aWVtcG8gZXN0w6FuIGVuIHVuIGNoYXJ0PyANCg0KYGBge3J9DQojIGNhbnRpZGFkIGRlIHNlbWFuYXMgcXVlIGVzdHV2aWVyb24gZW4gZWwgY2hhcnQNCg0KZGZfY2hhcnRzICU+JSANCiAgbXV0YXRlKHdlZWtfc3RhcnQ9YXMuRGF0ZSh3ZWVrX3N0YXJ0KSwNCiAgICAgICAgIHdlZWtfZW5kID0gYXMuRGF0ZSh3ZWVrX2VuZCksDQogICAgICAgICB3ZWVrX3llYXIgPSAoeWVhcih3ZWVrX3N0YXJ0KSkpICU+JQ0KICBhcnJhbmdlKEFydGlzdCwgVHJhY2tfTmFtZSkgJT4lIA0KICBncm91cF9ieShBcnRpc3QsIFRyYWNrX05hbWUsIFVSTCkgJT4lIA0KIGRwbHlyOjogc3VtbWFyaXNlKCBkYXlfaW4gPSBtaW4od2Vla19zdGFydCksDQogICAgICAgICAgICAgeWVhcl9pbiA9IHllYXIoZGF5X2luKSwNCiAgICAgICAgICAgICBkYXlfbWF4ID0gbWF4KHdlZWtfZW5kKSwNCiAgICAgICAgICAgICB5ZWFyX21heCA9IHllYXIoZGF5X21heCksDQogICAgICAgICAgICAgZHVyYWNpb25fY2hhcnRfZGlhcyA9IGRheV9tYXgtZGF5X2luLA0KICAgICAgICAgICAgIGR1cmFjaW9uX2NoYXJ0X2FuaW8gPSB5ZWFyX21heCAtIHllYXJfaW4pICU+JSANCiAgYXJyYW5nZShBcnRpc3QpDQoNCmBgYA0KDQo=